doing 1.0.91 → 2.0.3.pre
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/AUTHORS +19 -0
 - data/CHANGELOG.md +590 -0
 - data/COMMANDS.md +1181 -0
 - data/Gemfile +2 -0
 - data/Gemfile.lock +110 -0
 - data/LICENSE +23 -0
 - data/README.md +15 -699
 - data/Rakefile +79 -0
 - data/_config.yml +1 -0
 - data/bin/doing +1012 -486
 - data/doing.fish +278 -0
 - data/doing.gemspec +34 -0
 - data/doing.rdoc +1759 -0
 - data/example_plugin.rb +209 -0
 - data/generate_completions.sh +4 -0
 - data/img/doing-colors.jpg +0 -0
 - data/img/doing-printf-wrap-800.jpg +0 -0
 - data/img/doing-show-note-formatting-800.jpg +0 -0
 - data/lib/completion/_doing.zsh +151 -0
 - data/lib/completion/doing.bash +416 -0
 - data/lib/completion/doing.fish +278 -0
 - data/lib/doing/array.rb +8 -0
 - data/lib/doing/cli_status.rb +66 -0
 - data/lib/doing/colors.rb +136 -0
 - data/lib/doing/configuration.rb +310 -0
 - data/lib/doing/errors.rb +102 -0
 - data/lib/doing/hash.rb +31 -0
 - data/lib/doing/hooks.rb +59 -0
 - data/lib/doing/item.rb +155 -0
 - data/lib/doing/log_adapter.rb +342 -0
 - data/lib/doing/markdown_document_listener.rb +174 -0
 - data/lib/doing/note.rb +59 -0
 - data/lib/doing/pager.rb +95 -0
 - data/lib/doing/plugin_manager.rb +208 -0
 - data/lib/doing/plugins/export/csv_export.rb +48 -0
 - data/lib/doing/plugins/export/html_export.rb +83 -0
 - data/lib/doing/plugins/export/json_export.rb +140 -0
 - data/lib/doing/plugins/export/markdown_export.rb +85 -0
 - data/lib/doing/plugins/export/taskpaper_export.rb +34 -0
 - data/lib/doing/plugins/export/template_export.rb +141 -0
 - data/lib/doing/plugins/import/cal_to_json.scpt +0 -0
 - data/lib/doing/plugins/import/calendar_import.rb +76 -0
 - data/lib/doing/plugins/import/doing_import.rb +144 -0
 - data/lib/doing/plugins/import/timing_import.rb +78 -0
 - data/lib/doing/string.rb +346 -0
 - data/lib/doing/symbol.rb +16 -0
 - data/lib/doing/time.rb +18 -0
 - data/lib/doing/util.rb +186 -0
 - data/lib/doing/version.rb +1 -1
 - data/lib/doing/wwid.rb +1831 -2358
 - data/lib/doing/wwidfile.rb +117 -0
 - data/lib/doing.rb +44 -4
 - data/lib/examples/commands/wiki.rb +80 -0
 - data/lib/examples/plugins/hooks.rb +22 -0
 - data/lib/examples/plugins/say_export.rb +202 -0
 - data/lib/examples/plugins/templates/wiki.css +169 -0
 - data/lib/examples/plugins/templates/wiki.haml +27 -0
 - data/lib/examples/plugins/templates/wiki_index.haml +18 -0
 - data/lib/examples/plugins/wiki_export.rb +87 -0
 - data/lib/templates/doing-markdown.erb +5 -0
 - data/man/doing.1 +964 -0
 - data/man/doing.1.html +711 -0
 - data/man/doing.1.ronn +600 -0
 - data/package-lock.json +3 -0
 - data/rdoc_to_mmd.rb +42 -0
 - data/rdocfixer.rb +13 -0
 - data/scripts/generate_bash_completions.rb +210 -0
 - data/scripts/generate_fish_completions.rb +201 -0
 - data/scripts/generate_zsh_completions.rb +164 -0
 - metadata +82 -6
 - data/lib/doing/helpers.rb +0 -191
 
    
        data/bin/doing
    CHANGED
    
    | 
         @@ -20,17 +20,49 @@ if class_exists? 'Encoding' 
     | 
|
| 
       20 
20 
     | 
    
         
             
            end
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
            include GLI::App
         
     | 
| 
      
 23 
     | 
    
         
            +
            include Doing::Errors
         
     | 
| 
       23 
24 
     | 
    
         
             
            version Doing::VERSION
         
     | 
| 
      
 25 
     | 
    
         
            +
            hide_commands_without_desc true
         
     | 
| 
      
 26 
     | 
    
         
            +
            autocomplete_commands true
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            REGEX_BOOL = /^(?:and|all|any|or|not|none)$/i
         
     | 
| 
      
 29 
     | 
    
         
            +
            REGEX_SORT_ORDER = /^(?:a(?:sc)?|d(?:esc)?)$/i
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            InvalidExportType = Class.new(RuntimeError)
         
     | 
| 
      
 32 
     | 
    
         
            +
            MissingConfigFile = Class.new(RuntimeError)
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            colors = Doing::Color
         
     | 
| 
      
 35 
     | 
    
         
            +
            wwid = Doing::WWID.new
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            Doing.logger.log_level = :info
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            if ENV['DOING_LOG_LEVEL'] || ENV['DOING_DEBUG'] || ENV['DOING_QUIET'] || ENV['DOING_VERBOSE'] || ENV['DOING_PLUGIN_DEBUG']
         
     | 
| 
      
 40 
     | 
    
         
            +
              # Quiet always wins
         
     | 
| 
      
 41 
     | 
    
         
            +
              if ENV['DOING_QUIET'] && ENV['DOING_QUIET'].truthy?
         
     | 
| 
      
 42 
     | 
    
         
            +
                Doing.logger.log_level = :error
         
     | 
| 
      
 43 
     | 
    
         
            +
              elsif (ENV['DOING_PLUGIN_DEBUG'] && ENV['DOING_PLUGIN_DEBUG'].truthy?)
         
     | 
| 
      
 44 
     | 
    
         
            +
                Doing.logger.log_level = :debug
         
     | 
| 
      
 45 
     | 
    
         
            +
              elsif (ENV['DOING_DEBUG'] && ENV['DOING_DEBUG'].truthy?)
         
     | 
| 
      
 46 
     | 
    
         
            +
                Doing.logger.log_level = :debug
         
     | 
| 
      
 47 
     | 
    
         
            +
              elsif ENV['DOING_LOG_LEVEL']
         
     | 
| 
      
 48 
     | 
    
         
            +
                Doing.logger.log_level = ENV['DOING_LOG_LEVEL']
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
       24 
51 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
            wwid. 
     | 
| 
      
 52 
     | 
    
         
            +
            if ENV['DOING_CONFIG']
         
     | 
| 
      
 53 
     | 
    
         
            +
              Doing.config_with(ENV['DOING_CONFIG'], { ignore_local: true })
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
            config = Doing.config
         
     | 
| 
      
 57 
     | 
    
         
            +
            settings = config.settings
         
     | 
| 
      
 58 
     | 
    
         
            +
            wwid.config = settings
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            if config.settings.dig('plugins', 'command_path')
         
     | 
| 
      
 61 
     | 
    
         
            +
              commands_from File.expand_path(config.settings.dig('plugins', 'command_path'))
         
     | 
| 
      
 62 
     | 
    
         
            +
            end
         
     | 
| 
       32 
63 
     | 
    
         | 
| 
       33 
64 
     | 
    
         
             
            program_desc 'A CLI for a What Was I Doing system'
         
     | 
| 
      
 65 
     | 
    
         
            +
            program_long_desc %(Doing uses a TaskPaper-like formatting to keep a plain text record of what you've been doing, complete with tag-based time tracking. The command line tool allows you to add entries, annotate with tags and notes, and view your entries with myriad options, with a focus on a "natural" language syntax.)
         
     | 
| 
       34 
66 
     | 
    
         | 
| 
       35 
67 
     | 
    
         
             
            default_command :recent
         
     | 
| 
       36 
68 
     | 
    
         
             
            # sort_help :manually
         
     | 
| 
         @@ -41,23 +73,49 @@ switch [:notes], default_value: true, negatable: true 
     | 
|
| 
       41 
73 
     | 
    
         
             
            desc 'Send results report to STDOUT instead of STDERR'
         
     | 
| 
       42 
74 
     | 
    
         
             
            switch [:stdout], default_value: false, negatable: false
         
     | 
| 
       43 
75 
     | 
    
         | 
| 
      
 76 
     | 
    
         
            +
            desc 'Use a pager when output is longer than screen'
         
     | 
| 
      
 77 
     | 
    
         
            +
            switch %i[p pager], default_value: settings['paginate']
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
            desc 'Answer yes/no menus with default option'
         
     | 
| 
      
 80 
     | 
    
         
            +
            switch [:default], default_value: false
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
       44 
82 
     | 
    
         
             
            desc 'Exclude auto tags and default tags'
         
     | 
| 
       45 
     | 
    
         
            -
            switch %i[x noauto], default_value: false
         
     | 
| 
      
 83 
     | 
    
         
            +
            switch %i[x noauto], default_value: false, negatable: false
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            desc 'Colored output'
         
     | 
| 
      
 86 
     | 
    
         
            +
            switch %i[color], default_value: true
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
            desc 'Silence info messages'
         
     | 
| 
      
 89 
     | 
    
         
            +
            switch %i[q quiet], default_value: false, negatable: false
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
            desc 'Verbose output'
         
     | 
| 
      
 92 
     | 
    
         
            +
            switch %i[debug], default_value: false, negatable: false
         
     | 
| 
       46 
93 
     | 
    
         | 
| 
       47 
     | 
    
         
            -
            desc 'Use a specific configuration file'
         
     | 
| 
       48 
     | 
    
         
            -
            flag [:config_file], default_value:  
     | 
| 
      
 94 
     | 
    
         
            +
            desc 'Use a specific configuration file. Deprecated, set $DOING_CONFIG instead.'
         
     | 
| 
      
 95 
     | 
    
         
            +
            flag [:config_file], default_value: config.config_file
         
     | 
| 
       49 
96 
     | 
    
         | 
| 
       50 
97 
     | 
    
         
             
            desc 'Specify a different doing_file'
         
     | 
| 
       51 
98 
     | 
    
         
             
            flag %i[f doing_file]
         
     | 
| 
       52 
99 
     | 
    
         | 
| 
       53 
100 
     | 
    
         
             
            desc 'Add an entry'
         
     | 
| 
      
 101 
     | 
    
         
            +
            long_desc %(Record what you're starting now, or backdate the start time using natural language.
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
            A parenthetical at the end of the entry will be converted to a note.
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
            Run with no argument to create a new entry using #{Doing::Util.default_editor}.)
         
     | 
| 
       54 
106 
     | 
    
         
             
            arg_name 'ENTRY'
         
     | 
| 
       55 
107 
     | 
    
         
             
            command %i[now next] do |c|
         
     | 
| 
      
 108 
     | 
    
         
            +
              c.example 'doing now', desc: "Open #{Doing::Util.default_editor} to input an entry and optional note."
         
     | 
| 
      
 109 
     | 
    
         
            +
              c.example 'doing now working on a new project', desc: 'Add a new entry at the current time'
         
     | 
| 
      
 110 
     | 
    
         
            +
              c.example 'doing now debugging @project2', desc: 'Add an entry with a tag'
         
     | 
| 
      
 111 
     | 
    
         
            +
              c.example 'doing now adding an entry (with a note)', desc: 'Parenthetical at end is converted to note'
         
     | 
| 
      
 112 
     | 
    
         
            +
              c.example 'doing now --back 2pm A thing I started at 2:00 and am still doing...', desc: 'Backdate an entry'
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
       56 
114 
     | 
    
         
             
              c.desc 'Section'
         
     | 
| 
       57 
115 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       58 
116 
     | 
    
         
             
              c.flag %i[s section]
         
     | 
| 
       59 
117 
     | 
    
         | 
| 
       60 
     | 
    
         
            -
              c.desc "Edit entry with #{ 
     | 
| 
      
 118 
     | 
    
         
            +
              c.desc "Edit entry with #{Doing::Util.default_editor}"
         
     | 
| 
       61 
119 
     | 
    
         
             
              c.switch %i[e editor], negatable: false, default_value: false
         
     | 
| 
       62 
120 
     | 
    
         | 
| 
       63 
121 
     | 
    
         
             
              c.desc 'Backdate start time [4pm|20m|2h|yesterday noon]'
         
     | 
| 
         @@ -67,7 +125,7 @@ command %i[now next] do |c| 
     | 
|
| 
       67 
125 
     | 
    
         
             
              c.desc 'Timed entry, marks last entry in section as @done'
         
     | 
| 
       68 
126 
     | 
    
         
             
              c.switch %i[f finish_last], negatable: false, default_value: false
         
     | 
| 
       69 
127 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
              c.desc ' 
     | 
| 
      
 128 
     | 
    
         
            +
              c.desc 'Include a note'
         
     | 
| 
       71 
129 
     | 
    
         
             
              c.arg_name 'TEXT'
         
     | 
| 
       72 
130 
     | 
    
         
             
              c.flag %i[n note]
         
     | 
| 
       73 
131 
     | 
    
         | 
| 
         @@ -77,9 +135,9 @@ command %i[now next] do |c| 
     | 
|
| 
       77 
135 
     | 
    
         | 
| 
       78 
136 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       79 
137 
     | 
    
         
             
                if options[:back]
         
     | 
| 
       80 
     | 
    
         
            -
                  date = wwid.chronify(options[:back])
         
     | 
| 
      
 138 
     | 
    
         
            +
                  date = wwid.chronify(options[:back], guess: :begin)
         
     | 
| 
       81 
139 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
                   
     | 
| 
      
 140 
     | 
    
         
            +
                  raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string' if date.nil?
         
     | 
| 
       83 
141 
     | 
    
         
             
                else
         
     | 
| 
       84 
142 
     | 
    
         
             
                  date = Time.now
         
     | 
| 
       85 
143 
     | 
    
         
             
                end
         
     | 
| 
         @@ -87,17 +145,17 @@ command %i[now next] do |c| 
     | 
|
| 
       87 
145 
     | 
    
         
             
                if options[:section]
         
     | 
| 
       88 
146 
     | 
    
         
             
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
       89 
147 
     | 
    
         
             
                else
         
     | 
| 
       90 
     | 
    
         
            -
                  options[:section] =  
     | 
| 
      
 148 
     | 
    
         
            +
                  options[:section] = settings['current_section']
         
     | 
| 
       91 
149 
     | 
    
         
             
                end
         
     | 
| 
       92 
150 
     | 
    
         | 
| 
       93 
151 
     | 
    
         
             
                if options[:e] || (args.empty? && $stdin.stat.size.zero?)
         
     | 
| 
       94 
     | 
    
         
            -
                   
     | 
| 
      
 152 
     | 
    
         
            +
                  raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
         
     | 
| 
       95 
153 
     | 
    
         | 
| 
       96 
154 
     | 
    
         
             
                  input = ''
         
     | 
| 
       97 
155 
     | 
    
         
             
                  input += args.join(' ') unless args.empty?
         
     | 
| 
       98 
156 
     | 
    
         
             
                  input = wwid.fork_editor(input).strip
         
     | 
| 
       99 
157 
     | 
    
         | 
| 
       100 
     | 
    
         
            -
                   
     | 
| 
      
 158 
     | 
    
         
            +
                  raise Doing::Errors::EmptyInput, 'No content' if input.empty?
         
     | 
| 
       101 
159 
     | 
    
         | 
| 
       102 
160 
     | 
    
         
             
                  title, note = wwid.format_input(input)
         
     | 
| 
       103 
161 
     | 
    
         
             
                  note.push(options[:n]) if options[:n]
         
     | 
| 
         @@ -115,14 +173,76 @@ command %i[now next] do |c| 
     | 
|
| 
       115 
173 
     | 
    
         
             
                  wwid.add_item(title.cap_first, section, { note: note, back: date, timed: options[:f] })
         
     | 
| 
       116 
174 
     | 
    
         
             
                  wwid.write(wwid.doing_file)
         
     | 
| 
       117 
175 
     | 
    
         
             
                else
         
     | 
| 
       118 
     | 
    
         
            -
                   
     | 
| 
      
 176 
     | 
    
         
            +
                  raise Doing::Errors::EmptyInput, 'You must provide content when creating a new entry'
         
     | 
| 
      
 177 
     | 
    
         
            +
                end
         
     | 
| 
      
 178 
     | 
    
         
            +
              end
         
     | 
| 
      
 179 
     | 
    
         
            +
            end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
            desc 'Reset the start time of an entry'
         
     | 
| 
      
 182 
     | 
    
         
            +
            command %i[reset begin] do |c|
         
     | 
| 
      
 183 
     | 
    
         
            +
              c.desc 'Set the start date of an item to now'
         
     | 
| 
      
 184 
     | 
    
         
            +
              c.arg_name 'NAME'
         
     | 
| 
      
 185 
     | 
    
         
            +
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
              c.desc 'Resume entry (remove @done)'
         
     | 
| 
      
 188 
     | 
    
         
            +
              c.switch %i[r resume], default_value: true
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
              c.desc 'Reset last entry matching tag'
         
     | 
| 
      
 191 
     | 
    
         
            +
              c.arg_name 'TAG'
         
     | 
| 
      
 192 
     | 
    
         
            +
              c.flag [:tag]
         
     | 
| 
      
 193 
     | 
    
         
            +
             
     | 
| 
      
 194 
     | 
    
         
            +
              c.desc 'Reset last entry matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
         
     | 
| 
      
 195 
     | 
    
         
            +
              c.arg_name 'QUERY'
         
     | 
| 
      
 196 
     | 
    
         
            +
              c.flag [:search]
         
     | 
| 
      
 197 
     | 
    
         
            +
             
     | 
| 
      
 198 
     | 
    
         
            +
              c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
         
     | 
| 
      
 199 
     | 
    
         
            +
              c.arg_name 'BOOLEAN'
         
     | 
| 
      
 200 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
              c.desc 'Select from a menu of matching entries'
         
     | 
| 
      
 203 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
              c.action do |global_options, options, args|
         
     | 
| 
      
 206 
     | 
    
         
            +
                if options[:section]
         
     | 
| 
      
 207 
     | 
    
         
            +
                  options[:section] = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
      
 208 
     | 
    
         
            +
                end
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
                options[:tag_bool] = options[:bool].normalize_bool
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                items = wwid.filter_items([], opt: options)
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                if options[:interactive]
         
     | 
| 
      
 215 
     | 
    
         
            +
                  last_entry = wwid.choose_from_items(items, {
         
     | 
| 
      
 216 
     | 
    
         
            +
                    menu: true,
         
     | 
| 
      
 217 
     | 
    
         
            +
                    header: '',
         
     | 
| 
      
 218 
     | 
    
         
            +
                    prompt: 'Select an entry to start/reset > ',
         
     | 
| 
      
 219 
     | 
    
         
            +
                    multiple: false,
         
     | 
| 
      
 220 
     | 
    
         
            +
                    sort: false,
         
     | 
| 
      
 221 
     | 
    
         
            +
                    show_if_single: true
         
     | 
| 
      
 222 
     | 
    
         
            +
                  }, include_section: options[:section].nil? )
         
     | 
| 
      
 223 
     | 
    
         
            +
                else
         
     | 
| 
      
 224 
     | 
    
         
            +
                  last_entry = items.last
         
     | 
| 
       119 
225 
     | 
    
         
             
                end
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
      
 227 
     | 
    
         
            +
                unless last_entry
         
     | 
| 
      
 228 
     | 
    
         
            +
                  Doing.logger.warn('Not found:', 'No entry matching parameters was found.')
         
     | 
| 
      
 229 
     | 
    
         
            +
                  return
         
     | 
| 
      
 230 
     | 
    
         
            +
                end
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                wwid.reset_item(last_entry, resume: options[:resume])
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
                # new_entry = Doing::Item.new(last_entry.date, last_entry.title, last_entry.section, new_note)
         
     | 
| 
      
 235 
     | 
    
         
            +
             
     | 
| 
      
 236 
     | 
    
         
            +
                wwid.write(wwid.doing_file)
         
     | 
| 
       120 
237 
     | 
    
         
             
              end
         
     | 
| 
       121 
238 
     | 
    
         
             
            end
         
     | 
| 
       122 
239 
     | 
    
         | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
       123 
241 
     | 
    
         
             
            desc 'Add a note to the last entry'
         
     | 
| 
       124 
242 
     | 
    
         
             
            long_desc %(
         
     | 
| 
       125 
     | 
    
         
            -
              If -r is provided with no other arguments, the last note is removed. 
     | 
| 
      
 243 
     | 
    
         
            +
              If -r is provided with no other arguments, the last note is removed.
         
     | 
| 
      
 244 
     | 
    
         
            +
              If new content is specified through arguments or STDIN, any previous
         
     | 
| 
      
 245 
     | 
    
         
            +
              note will be replaced with the new one.
         
     | 
| 
       126 
246 
     | 
    
         | 
| 
       127 
247 
     | 
    
         
             
              Use -e to load the last entry in a text editor where you can append a note.
         
     | 
| 
       128 
248 
     | 
    
         
             
            )
         
     | 
| 
         @@ -132,47 +252,77 @@ command :note do |c| 
     | 
|
| 
       132 
252 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       133 
253 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
       134 
254 
     | 
    
         | 
| 
       135 
     | 
    
         
            -
              c.desc "Edit entry with #{ 
     | 
| 
      
 255 
     | 
    
         
            +
              c.desc "Edit entry with #{Doing::Util.default_editor}"
         
     | 
| 
       136 
256 
     | 
    
         
             
              c.switch %i[e editor], negatable: false, default_value: false
         
     | 
| 
       137 
257 
     | 
    
         | 
| 
       138 
258 
     | 
    
         
             
              c.desc "Replace/Remove last entry's note (default append)"
         
     | 
| 
       139 
259 
     | 
    
         
             
              c.switch %i[r remove], negatable: false, default_value: false
         
     | 
| 
       140 
260 
     | 
    
         | 
| 
      
 261 
     | 
    
         
            +
              c.desc 'Add/remove note from last entry matching tag'
         
     | 
| 
      
 262 
     | 
    
         
            +
              c.arg_name 'TAG'
         
     | 
| 
      
 263 
     | 
    
         
            +
              c.flag [:tag]
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
              c.desc 'Add/remove note from last entry matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
         
     | 
| 
      
 266 
     | 
    
         
            +
              c.arg_name 'QUERY'
         
     | 
| 
      
 267 
     | 
    
         
            +
              c.flag [:search]
         
     | 
| 
      
 268 
     | 
    
         
            +
             
     | 
| 
      
 269 
     | 
    
         
            +
              c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
         
     | 
| 
      
 270 
     | 
    
         
            +
              c.arg_name 'BOOLEAN'
         
     | 
| 
      
 271 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
      
 273 
     | 
    
         
            +
              c.desc 'Select item for new note from a menu of matching entries'
         
     | 
| 
      
 274 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
       141 
276 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       142 
277 
     | 
    
         
             
                if options[:section]
         
     | 
| 
       143 
     | 
    
         
            -
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
      
 278 
     | 
    
         
            +
                  options[:section] = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
      
 279 
     | 
    
         
            +
                end
         
     | 
| 
      
 280 
     | 
    
         
            +
             
     | 
| 
      
 281 
     | 
    
         
            +
                options[:tag_bool] = options[:bool].normalize_bool
         
     | 
| 
      
 282 
     | 
    
         
            +
             
     | 
| 
      
 283 
     | 
    
         
            +
                last_entry = wwid.last_entry(options)
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
                unless last_entry
         
     | 
| 
      
 286 
     | 
    
         
            +
                  Doing.logger.warn('Not found:', 'No entry matching parameters was found.')
         
     | 
| 
      
 287 
     | 
    
         
            +
                  return
         
     | 
| 
       144 
288 
     | 
    
         
             
                end
         
     | 
| 
       145 
289 
     | 
    
         | 
| 
      
 290 
     | 
    
         
            +
                last_note = last_entry.note || Doing::Note.new
         
     | 
| 
      
 291 
     | 
    
         
            +
                new_note = Doing::Note.new
         
     | 
| 
      
 292 
     | 
    
         
            +
             
     | 
| 
       146 
293 
     | 
    
         
             
                if options[:e] || (args.empty? && $stdin.stat.size.zero? && !options[:r])
         
     | 
| 
       147 
     | 
    
         
            -
                   
     | 
| 
      
 294 
     | 
    
         
            +
                  raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
         
     | 
| 
       148 
295 
     | 
    
         | 
| 
       149 
296 
     | 
    
         
             
                  input = !args.empty? ? args.join(' ') : ''
         
     | 
| 
       150 
297 
     | 
    
         | 
| 
       151 
     | 
    
         
            -
                   
     | 
| 
       152 
     | 
    
         
            -
             
     | 
| 
       153 
     | 
    
         
            -
                   
     | 
| 
      
 298 
     | 
    
         
            +
                  if options[:remove]
         
     | 
| 
      
 299 
     | 
    
         
            +
                    prev_input = Doing::Note.new
         
     | 
| 
      
 300 
     | 
    
         
            +
                  else
         
     | 
| 
      
 301 
     | 
    
         
            +
                    prev_input = last_entry.note || Doing::Note.new
         
     | 
| 
      
 302 
     | 
    
         
            +
                  end
         
     | 
| 
       154 
303 
     | 
    
         | 
| 
       155 
     | 
    
         
            -
                  input =  
     | 
| 
       156 
     | 
    
         
            -
                  exit_now! 'No content, cancelled' unless input
         
     | 
| 
      
 304 
     | 
    
         
            +
                  input = prev_input.add(input)
         
     | 
| 
       157 
305 
     | 
    
         | 
| 
      
 306 
     | 
    
         
            +
                  input = wwid.fork_editor([last_entry.title, '### Edit below this line', input.to_s].join("\n")).strip
         
     | 
| 
       158 
307 
     | 
    
         
             
                  _title, note = wwid.format_input(input)
         
     | 
| 
       159 
     | 
    
         
            -
             
     | 
| 
       160 
     | 
    
         
            -
                   
     | 
| 
       161 
     | 
    
         
            -
             
     | 
| 
       162 
     | 
    
         
            -
                  wwid.note_last(section, note, replace: true)
         
     | 
| 
      
 308 
     | 
    
         
            +
                  options[:remove] = true
         
     | 
| 
      
 309 
     | 
    
         
            +
                  new_note.add(note)
         
     | 
| 
       163 
310 
     | 
    
         
             
                elsif !args.empty?
         
     | 
| 
       164 
     | 
    
         
            -
                   
     | 
| 
       165 
     | 
    
         
            -
                  note.insert(0, title)
         
     | 
| 
       166 
     | 
    
         
            -
                  wwid.note_last(section, note, replace: options[:r])
         
     | 
| 
      
 311 
     | 
    
         
            +
                  new_note.add(args.join(' '))
         
     | 
| 
       167 
312 
     | 
    
         
             
                elsif $stdin.stat.size.positive?
         
     | 
| 
       168 
     | 
    
         
            -
                   
     | 
| 
       169 
     | 
    
         
            -
                  note.insert(0, title)
         
     | 
| 
       170 
     | 
    
         
            -
                  wwid.note_last(section, note, replace: options[:r])
         
     | 
| 
       171 
     | 
    
         
            -
                elsif options[:r]
         
     | 
| 
       172 
     | 
    
         
            -
                  wwid.note_last(section, [], replace: true)
         
     | 
| 
      
 313 
     | 
    
         
            +
                  new_note.add($stdin.read)
         
     | 
| 
       173 
314 
     | 
    
         
             
                else
         
     | 
| 
       174 
     | 
    
         
            -
                   
     | 
| 
      
 315 
     | 
    
         
            +
                  raise Doing::Errors::EmptyInput, 'You must provide content when adding a note' unless options[:remove]
         
     | 
| 
       175 
316 
     | 
    
         
             
                end
         
     | 
| 
      
 317 
     | 
    
         
            +
             
     | 
| 
      
 318 
     | 
    
         
            +
                if last_note.equal?(new_note)
         
     | 
| 
      
 319 
     | 
    
         
            +
                  Doing.logger.debug('Skipped:', 'No note change')
         
     | 
| 
      
 320 
     | 
    
         
            +
                else
         
     | 
| 
      
 321 
     | 
    
         
            +
                  last_note.add(new_note, replace: options[:remove])
         
     | 
| 
      
 322 
     | 
    
         
            +
                  Doing.logger.info('Entry updated:', last_entry.title)
         
     | 
| 
      
 323 
     | 
    
         
            +
                end
         
     | 
| 
      
 324 
     | 
    
         
            +
                # new_entry = Doing::Item.new(last_entry.date, last_entry.title, last_entry.section, new_note)
         
     | 
| 
      
 325 
     | 
    
         
            +
             
     | 
| 
       176 
326 
     | 
    
         
             
                wwid.write(wwid.doing_file)
         
     | 
| 
       177 
327 
     | 
    
         
             
              end
         
     | 
| 
       178 
328 
     | 
    
         
             
            end
         
     | 
| 
         @@ -184,7 +334,7 @@ command :meanwhile do |c| 
     | 
|
| 
       184 
334 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       185 
335 
     | 
    
         
             
              c.flag %i[s section]
         
     | 
| 
       186 
336 
     | 
    
         | 
| 
       187 
     | 
    
         
            -
              c.desc "Edit entry with #{ 
     | 
| 
      
 337 
     | 
    
         
            +
              c.desc "Edit entry with #{Doing::Util.default_editor}"
         
     | 
| 
       188 
338 
     | 
    
         
             
              c.switch %i[e editor], negatable: false, default_value: false
         
     | 
| 
       189 
339 
     | 
    
         | 
| 
       190 
340 
     | 
    
         
             
              c.desc 'Archive previous @meanwhile entry'
         
     | 
| 
         @@ -200,9 +350,9 @@ command :meanwhile do |c| 
     | 
|
| 
       200 
350 
     | 
    
         | 
| 
       201 
351 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       202 
352 
     | 
    
         
             
                if options[:back]
         
     | 
| 
       203 
     | 
    
         
            -
                  date = wwid.chronify(options[:back])
         
     | 
| 
      
 353 
     | 
    
         
            +
                  date = wwid.chronify(options[:back], guess: :begin)
         
     | 
| 
       204 
354 
     | 
    
         | 
| 
       205 
     | 
    
         
            -
                   
     | 
| 
      
 355 
     | 
    
         
            +
                  raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string' if date.nil?
         
     | 
| 
       206 
356 
     | 
    
         
             
                else
         
     | 
| 
       207 
357 
     | 
    
         
             
                  date = Time.now
         
     | 
| 
       208 
358 
     | 
    
         
             
                end
         
     | 
| 
         @@ -210,12 +360,12 @@ command :meanwhile do |c| 
     | 
|
| 
       210 
360 
     | 
    
         
             
                if options[:section]
         
     | 
| 
       211 
361 
     | 
    
         
             
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
       212 
362 
     | 
    
         
             
                else
         
     | 
| 
       213 
     | 
    
         
            -
                  section =  
     | 
| 
      
 363 
     | 
    
         
            +
                  section = settings['current_section']
         
     | 
| 
       214 
364 
     | 
    
         
             
                end
         
     | 
| 
       215 
365 
     | 
    
         
             
                input = ''
         
     | 
| 
       216 
366 
     | 
    
         | 
| 
       217 
367 
     | 
    
         
             
                if options[:e]
         
     | 
| 
       218 
     | 
    
         
            -
                   
     | 
| 
      
 368 
     | 
    
         
            +
                  raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
         
     | 
| 
       219 
369 
     | 
    
         | 
| 
       220 
370 
     | 
    
         
             
                  input += args.join(' ') unless args.empty?
         
     | 
| 
       221 
371 
     | 
    
         
             
                  input = wwid.fork_editor(input).strip
         
     | 
| 
         @@ -248,30 +398,55 @@ long_desc %( 
     | 
|
| 
       248 
398 
     | 
    
         
             
              Templates are printed to STDOUT for piping to a file.
         
     | 
| 
       249 
399 
     | 
    
         
             
              Save them and use them in the configuration file under html_template.
         
     | 
| 
       250 
400 
     | 
    
         | 
| 
       251 
     | 
    
         
            -
              Example `doing template  
     | 
| 
      
 401 
     | 
    
         
            +
              Example `doing template haml > ~/styles/my_doing.haml`
         
     | 
| 
       252 
402 
     | 
    
         
             
            )
         
     | 
| 
       253 
     | 
    
         
            -
            arg_name 'TYPE', must_match:  
     | 
| 
      
 403 
     | 
    
         
            +
            arg_name 'TYPE', must_match: Doing::Plugins.template_regex
         
     | 
| 
       254 
404 
     | 
    
         
             
            command :template do |c|
         
     | 
| 
      
 405 
     | 
    
         
            +
              c.desc 'List all available templates'
         
     | 
| 
      
 406 
     | 
    
         
            +
              c.switch %i[l list]
         
     | 
| 
      
 407 
     | 
    
         
            +
             
     | 
| 
      
 408 
     | 
    
         
            +
              c.desc 'List in single column for completion'
         
     | 
| 
      
 409 
     | 
    
         
            +
              c.switch %i[c]
         
     | 
| 
      
 410 
     | 
    
         
            +
             
     | 
| 
       255 
411 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       256 
     | 
    
         
            -
                 
     | 
| 
       257 
     | 
    
         
            -
             
     | 
| 
       258 
     | 
    
         
            -
             
     | 
| 
       259 
     | 
    
         
            -
             
     | 
| 
       260 
     | 
    
         
            -
             
     | 
| 
       261 
     | 
    
         
            -
             
     | 
| 
       262 
     | 
    
         
            -
                   
     | 
| 
       263 
     | 
    
         
            -
                 
     | 
| 
       264 
     | 
    
         
            -
             
     | 
| 
      
 412 
     | 
    
         
            +
                if options[:list] || options[:c]
         
     | 
| 
      
 413 
     | 
    
         
            +
                  if options[:c]
         
     | 
| 
      
 414 
     | 
    
         
            +
                    $stdout.print Doing::Plugins.plugin_templates.join("\n")
         
     | 
| 
      
 415 
     | 
    
         
            +
                  else
         
     | 
| 
      
 416 
     | 
    
         
            +
                    $stdout.puts "Available templates: #{Doing::Plugins.plugin_templates.join(', ')}"
         
     | 
| 
      
 417 
     | 
    
         
            +
                  end
         
     | 
| 
      
 418 
     | 
    
         
            +
                  return
         
     | 
| 
      
 419 
     | 
    
         
            +
                end
         
     | 
| 
      
 420 
     | 
    
         
            +
             
     | 
| 
      
 421 
     | 
    
         
            +
                if args.empty?
         
     | 
| 
      
 422 
     | 
    
         
            +
                  type = wwid.choose_from(Doing::Plugins.plugin_templates, sorted: false, prompt: 'Select template type > ')
         
     | 
| 
       265 
423 
     | 
    
         
             
                else
         
     | 
| 
       266 
     | 
    
         
            -
                   
     | 
| 
      
 424 
     | 
    
         
            +
                  type = args[0]
         
     | 
| 
       267 
425 
     | 
    
         
             
                end
         
     | 
| 
      
 426 
     | 
    
         
            +
             
     | 
| 
      
 427 
     | 
    
         
            +
                raise Doing::Errors::InvalidPluginType, "No type specified, use `doing template [#{Doing::Plugins.plugin_templates.join('|')}]`" unless type
         
     | 
| 
      
 428 
     | 
    
         
            +
             
     | 
| 
      
 429 
     | 
    
         
            +
                $stdout.puts Doing::Plugins.template_for_trigger(type)
         
     | 
| 
      
 430 
     | 
    
         
            +
             
     | 
| 
      
 431 
     | 
    
         
            +
                # case args[0]
         
     | 
| 
      
 432 
     | 
    
         
            +
                # when /html|haml/i
         
     | 
| 
      
 433 
     | 
    
         
            +
                #   $stdout.puts wwid.haml_template
         
     | 
| 
      
 434 
     | 
    
         
            +
                # when /css/i
         
     | 
| 
      
 435 
     | 
    
         
            +
                #   $stdout.puts wwid.css_template
         
     | 
| 
      
 436 
     | 
    
         
            +
                # when /markdown|md|erb/i
         
     | 
| 
      
 437 
     | 
    
         
            +
                #   $stdout.puts wwid.markdown_template
         
     | 
| 
      
 438 
     | 
    
         
            +
                # else
         
     | 
| 
      
 439 
     | 
    
         
            +
                #   exit_now! 'Invalid type specified, must be HAML or CSS'
         
     | 
| 
      
 440 
     | 
    
         
            +
                # end
         
     | 
| 
       268 
441 
     | 
    
         
             
              end
         
     | 
| 
       269 
442 
     | 
    
         
             
            end
         
     | 
| 
       270 
443 
     | 
    
         | 
| 
       271 
     | 
    
         
            -
            desc 'Display an interactive menu to perform operations 
     | 
| 
      
 444 
     | 
    
         
            +
            desc 'Display an interactive menu to perform operations'
         
     | 
| 
       272 
445 
     | 
    
         
             
            long_desc 'List all entries and select with typeahead fuzzy matching.
         
     | 
| 
       273 
446 
     | 
    
         | 
| 
       274 
     | 
    
         
            -
            Multiple selections are allowed, hit tab to add the highlighted entry to the 
     | 
| 
      
 447 
     | 
    
         
            +
            Multiple selections are allowed, hit tab to add the highlighted entry to the
         
     | 
| 
      
 448 
     | 
    
         
            +
            selection, and use ctrl-a to select all visible items. Return processes the
         
     | 
| 
      
 449 
     | 
    
         
            +
            selected entries.'
         
     | 
| 
       275 
450 
     | 
    
         
             
            command :select do |c|
         
     | 
| 
       276 
451 
     | 
    
         
             
              c.desc 'Select from a specific section'
         
     | 
| 
       277 
452 
     | 
    
         
             
              c.arg_name 'SECTION'
         
     | 
| 
         @@ -296,7 +471,7 @@ command :select do |c| 
     | 
|
| 
       296 
471 
     | 
    
         | 
| 
       297 
472 
     | 
    
         
             
              c.desc 'Initial search query for filtering. Matching is fuzzy. For exact matching, start query with a single quote, e.g. `--query "\'search"'
         
     | 
| 
       298 
473 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
       299 
     | 
    
         
            -
              c.flag %i[q query]
         
     | 
| 
      
 474 
     | 
    
         
            +
              c.flag %i[q query search]
         
     | 
| 
       300 
475 
     | 
    
         | 
| 
       301 
476 
     | 
    
         
             
              c.desc 'Use --no-menu to skip the interactive menu. Use with --query to filter items and act on results automatically. Test with `--output doing` to preview matches.'
         
     | 
| 
       302 
477 
     | 
    
         
             
              c.switch %i[menu], negatable: true, default_value: true
         
     | 
| 
         @@ -323,12 +498,17 @@ command :select do |c| 
     | 
|
| 
       323 
498 
     | 
    
         
             
              c.arg_name 'FILE'
         
     | 
| 
       324 
499 
     | 
    
         
             
              c.flag %i[save_to]
         
     | 
| 
       325 
500 
     | 
    
         | 
| 
       326 
     | 
    
         
            -
              c.desc  
     | 
| 
      
 501 
     | 
    
         
            +
              c.desc "Output entries to format (#{Doing::Plugins.plugin_names(type: :export)})"
         
     | 
| 
       327 
502 
     | 
    
         
             
              c.arg_name 'FORMAT'
         
     | 
| 
       328 
     | 
    
         
            -
              c.flag %i[o output] 
     | 
| 
      
 503 
     | 
    
         
            +
              c.flag %i[o output]
         
     | 
| 
      
 504 
     | 
    
         
            +
             
     | 
| 
      
 505 
     | 
    
         
            +
              c.desc "Copy selection as a new entry with current time and no @done tag. Only works with single selections. Can be combined with --editor."
         
     | 
| 
      
 506 
     | 
    
         
            +
              c.switch %i[again resume]
         
     | 
| 
       329 
507 
     | 
    
         | 
| 
       330 
508 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       331 
     | 
    
         
            -
                 
     | 
| 
      
 509 
     | 
    
         
            +
                raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
         
     | 
| 
      
 510 
     | 
    
         
            +
             
     | 
| 
      
 511 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, '--no-menu requires --query' if !options[:menu] && !options[:query]
         
     | 
| 
       332 
512 
     | 
    
         | 
| 
       333 
513 
     | 
    
         
             
                wwid.interactive(options)
         
     | 
| 
       334 
514 
     | 
    
         
             
              end
         
     | 
| 
         @@ -337,7 +517,7 @@ end 
     | 
|
| 
       337 
517 
     | 
    
         
             
            desc 'Add an item to the Later section'
         
     | 
| 
       338 
518 
     | 
    
         
             
            arg_name 'ENTRY'
         
     | 
| 
       339 
519 
     | 
    
         
             
            command :later do |c|
         
     | 
| 
       340 
     | 
    
         
            -
              c.desc "Edit entry with #{ 
     | 
| 
      
 520 
     | 
    
         
            +
              c.desc "Edit entry with #{Doing::Util.default_editor}"
         
     | 
| 
       341 
521 
     | 
    
         
             
              c.switch %i[e editor], negatable: false, default_value: false
         
     | 
| 
       342 
522 
     | 
    
         | 
| 
       343 
523 
     | 
    
         
             
              c.desc 'Backdate start time to date string [4pm|20m|2h|yesterday noon]'
         
     | 
| 
         @@ -350,18 +530,18 @@ command :later do |c| 
     | 
|
| 
       350 
530 
     | 
    
         | 
| 
       351 
531 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       352 
532 
     | 
    
         
             
                if options[:back]
         
     | 
| 
       353 
     | 
    
         
            -
                  date = wwid.chronify(options[:back])
         
     | 
| 
       354 
     | 
    
         
            -
                   
     | 
| 
      
 533 
     | 
    
         
            +
                  date = wwid.chronify(options[:back], guess: :begin)
         
     | 
| 
      
 534 
     | 
    
         
            +
                  raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string' if date.nil?
         
     | 
| 
       355 
535 
     | 
    
         
             
                else
         
     | 
| 
       356 
536 
     | 
    
         
             
                  date = Time.now
         
     | 
| 
       357 
537 
     | 
    
         
             
                end
         
     | 
| 
       358 
538 
     | 
    
         | 
| 
       359 
539 
     | 
    
         
             
                if options[:editor] || (args.empty? && $stdin.stat.size.zero?)
         
     | 
| 
       360 
     | 
    
         
            -
                   
     | 
| 
      
 540 
     | 
    
         
            +
                  raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
         
     | 
| 
       361 
541 
     | 
    
         | 
| 
       362 
542 
     | 
    
         
             
                  input = args.empty? ? '' : args.join(' ')
         
     | 
| 
       363 
543 
     | 
    
         
             
                  input = wwid.fork_editor(input).strip
         
     | 
| 
       364 
     | 
    
         
            -
                   
     | 
| 
      
 544 
     | 
    
         
            +
                  raise Doing::Errors::EmptyInput, 'No content' unless input && !input.empty?
         
     | 
| 
       365 
545 
     | 
    
         | 
| 
       366 
546 
     | 
    
         
             
                  title, note = wwid.format_input(input)
         
     | 
| 
       367 
547 
     | 
    
         
             
                  note.push(options[:n]) if options[:n]
         
     | 
| 
         @@ -378,7 +558,7 @@ command :later do |c| 
     | 
|
| 
       378 
558 
     | 
    
         
             
                  wwid.add_item(title.cap_first, 'Later', { note: note, back: date })
         
     | 
| 
       379 
559 
     | 
    
         
             
                  wwid.write(wwid.doing_file)
         
     | 
| 
       380 
560 
     | 
    
         
             
                else
         
     | 
| 
       381 
     | 
    
         
            -
                   
     | 
| 
      
 561 
     | 
    
         
            +
                  raise Doing::Errors::EmptyInput, 'You must provide content when creating a new entry'
         
     | 
| 
       382 
562 
     | 
    
         
             
                end
         
     | 
| 
       383 
563 
     | 
    
         
             
              end
         
     | 
| 
       384 
564 
     | 
    
         
             
            end
         
     | 
| 
         @@ -414,31 +594,39 @@ command %i[done did] do |c| 
     | 
|
| 
       414 
594 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       415 
595 
     | 
    
         
             
              c.flag %i[s section]
         
     | 
| 
       416 
596 
     | 
    
         | 
| 
       417 
     | 
    
         
            -
              c.desc "Edit entry with #{ 
     | 
| 
      
 597 
     | 
    
         
            +
              c.desc "Edit entry with #{Doing::Util.default_editor} (with no arguments, edits the last entry)"
         
     | 
| 
       418 
598 
     | 
    
         
             
              c.switch %i[e editor], negatable: false, default_value: false
         
     | 
| 
       419 
599 
     | 
    
         | 
| 
      
 600 
     | 
    
         
            +
              c.desc 'Include a note'
         
     | 
| 
      
 601 
     | 
    
         
            +
              c.arg_name 'TEXT'
         
     | 
| 
      
 602 
     | 
    
         
            +
              c.flag %i[n note]
         
     | 
| 
      
 603 
     | 
    
         
            +
             
     | 
| 
      
 604 
     | 
    
         
            +
              c.desc 'Finish last entry not already marked @done'
         
     | 
| 
      
 605 
     | 
    
         
            +
              c.switch %i[u unfinished], negatable: false, default_value: false
         
     | 
| 
      
 606 
     | 
    
         
            +
             
     | 
| 
       420 
607 
     | 
    
         
             
              # c.desc "Edit entry with specified app"
         
     | 
| 
       421 
608 
     | 
    
         
             
              # c.arg_name 'editor_app'
         
     | 
| 
       422 
609 
     | 
    
         
             
              # # c.flag [:a, :app]
         
     | 
| 
       423 
610 
     | 
    
         | 
| 
       424 
611 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       425 
612 
     | 
    
         
             
                took = 0
         
     | 
| 
      
 613 
     | 
    
         
            +
                donedate = nil
         
     | 
| 
       426 
614 
     | 
    
         | 
| 
       427 
615 
     | 
    
         
             
                if options[:took]
         
     | 
| 
       428 
616 
     | 
    
         
             
                  took = wwid.chronify_qty(options[:took])
         
     | 
| 
       429 
     | 
    
         
            -
                   
     | 
| 
      
 617 
     | 
    
         
            +
                  raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
         
     | 
| 
       430 
618 
     | 
    
         
             
                end
         
     | 
| 
       431 
619 
     | 
    
         | 
| 
       432 
620 
     | 
    
         
             
                if options[:back]
         
     | 
| 
       433 
     | 
    
         
            -
                  date = wwid.chronify(options[:back])
         
     | 
| 
       434 
     | 
    
         
            -
                   
     | 
| 
      
 621 
     | 
    
         
            +
                  date = wwid.chronify(options[:back], guess: :begin)
         
     | 
| 
      
 622 
     | 
    
         
            +
                  raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --back' if date.nil?
         
     | 
| 
       435 
623 
     | 
    
         
             
                else
         
     | 
| 
       436 
624 
     | 
    
         
             
                  date = options[:took] ? Time.now - took : Time.now
         
     | 
| 
       437 
625 
     | 
    
         
             
                end
         
     | 
| 
       438 
626 
     | 
    
         | 
| 
       439 
627 
     | 
    
         
             
                if options[:at]
         
     | 
| 
       440 
     | 
    
         
            -
                  finish_date = wwid.chronify(options[:at])
         
     | 
| 
       441 
     | 
    
         
            -
                   
     | 
| 
      
 628 
     | 
    
         
            +
                  finish_date = wwid.chronify(options[:at], guess: :begin)
         
     | 
| 
      
 629 
     | 
    
         
            +
                  raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
         
     | 
| 
       442 
630 
     | 
    
         | 
| 
       443 
631 
     | 
    
         
             
                  date = options[:took] ? finish_date - took : finish_date
         
     | 
| 
       444 
632 
     | 
    
         
             
                elsif options[:took]
         
     | 
| 
         @@ -449,58 +637,116 @@ command %i[done did] do |c| 
     | 
|
| 
       449 
637 
     | 
    
         
             
                  finish_date = Time.now
         
     | 
| 
       450 
638 
     | 
    
         
             
                end
         
     | 
| 
       451 
639 
     | 
    
         | 
| 
       452 
     | 
    
         
            -
                if  
     | 
| 
       453 
     | 
    
         
            -
                  donedate =  
     | 
| 
      
 640 
     | 
    
         
            +
                if options[:date]
         
     | 
| 
      
 641 
     | 
    
         
            +
                  donedate = finish_date.strftime('%F %R')
         
     | 
| 
       454 
642 
     | 
    
         
             
                end
         
     | 
| 
       455 
643 
     | 
    
         | 
| 
       456 
644 
     | 
    
         
             
                if options[:section]
         
     | 
| 
       457 
645 
     | 
    
         
             
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
       458 
646 
     | 
    
         
             
                else
         
     | 
| 
       459 
     | 
    
         
            -
                  section =  
     | 
| 
      
 647 
     | 
    
         
            +
                  section = settings['current_section']
         
     | 
| 
       460 
648 
     | 
    
         
             
                end
         
     | 
| 
       461 
649 
     | 
    
         | 
| 
      
 650 
     | 
    
         
            +
                note = Doing::Note.new
         
     | 
| 
      
 651 
     | 
    
         
            +
                note.add(options[:note]) if options[:note]
         
     | 
| 
      
 652 
     | 
    
         
            +
             
     | 
| 
       462 
653 
     | 
    
         
             
                if options[:editor]
         
     | 
| 
       463 
     | 
    
         
            -
                   
     | 
| 
      
 654 
     | 
    
         
            +
                  raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
         
     | 
| 
      
 655 
     | 
    
         
            +
                  is_new = false
         
     | 
| 
      
 656 
     | 
    
         
            +
             
     | 
| 
      
 657 
     | 
    
         
            +
                  if args.empty?
         
     | 
| 
      
 658 
     | 
    
         
            +
                    last_entry = wwid.filter_items([], opt: {unfinished: options[:unfinished], section: section, count: 1, age: 'new'}).max_by { |item| item.date }
         
     | 
| 
      
 659 
     | 
    
         
            +
             
     | 
| 
      
 660 
     | 
    
         
            +
                    unless last_entry
         
     | 
| 
      
 661 
     | 
    
         
            +
                      Doing.logger.debug('Skipped:', options[:unfinished] ? 'No unfinished entry' : 'Last entry already @done')
         
     | 
| 
      
 662 
     | 
    
         
            +
                      raise Doing::Errors::NoResults, 'No results'
         
     | 
| 
      
 663 
     | 
    
         
            +
                    end
         
     | 
| 
      
 664 
     | 
    
         
            +
             
     | 
| 
      
 665 
     | 
    
         
            +
                    old_entry = last_entry.dup
         
     | 
| 
      
 666 
     | 
    
         
            +
                    last_entry.note.add(note)
         
     | 
| 
      
 667 
     | 
    
         
            +
                    input = [last_entry.title, last_entry.note.to_s].join("\n")
         
     | 
| 
      
 668 
     | 
    
         
            +
                  else
         
     | 
| 
      
 669 
     | 
    
         
            +
                    is_new = true
         
     | 
| 
      
 670 
     | 
    
         
            +
                    input = [args.join(' '), note.to_s].join("\n")
         
     | 
| 
      
 671 
     | 
    
         
            +
                  end
         
     | 
| 
       464 
672 
     | 
    
         | 
| 
       465 
     | 
    
         
            -
                  input = ''
         
     | 
| 
       466 
     | 
    
         
            -
                  input += args.join(' ') unless args.empty?
         
     | 
| 
       467 
673 
     | 
    
         
             
                  input = wwid.fork_editor(input).strip
         
     | 
| 
       468 
     | 
    
         
            -
                   
     | 
| 
      
 674 
     | 
    
         
            +
                  raise Doing::Errors::EmptyInput, 'No content' unless input && !input.empty?
         
     | 
| 
       469 
675 
     | 
    
         | 
| 
       470 
676 
     | 
    
         
             
                  title, note = wwid.format_input(input)
         
     | 
| 
       471 
     | 
    
         
            -
                  title  
     | 
| 
       472 
     | 
    
         
            -
                   
     | 
| 
       473 
     | 
    
         
            -
             
     | 
| 
      
 677 
     | 
    
         
            +
                  new_entry = Doing::Item.new(date, title, section, note)
         
     | 
| 
      
 678 
     | 
    
         
            +
                  if new_entry.should_finish?
         
     | 
| 
      
 679 
     | 
    
         
            +
                    if new_entry.should_time?
         
     | 
| 
      
 680 
     | 
    
         
            +
                      new_entry.tag('done', value: donedate)
         
     | 
| 
      
 681 
     | 
    
         
            +
                    else
         
     | 
| 
      
 682 
     | 
    
         
            +
                      new_entry.tag('done')
         
     | 
| 
      
 683 
     | 
    
         
            +
                    end
         
     | 
| 
      
 684 
     | 
    
         
            +
                  end
         
     | 
| 
      
 685 
     | 
    
         
            +
             
     | 
| 
      
 686 
     | 
    
         
            +
                  if (is_new)
         
     | 
| 
      
 687 
     | 
    
         
            +
                    wwid.content[section][:items].push(new_entry)
         
     | 
| 
      
 688 
     | 
    
         
            +
                  else
         
     | 
| 
      
 689 
     | 
    
         
            +
                    wwid.update_item(old_entry, new_entry)
         
     | 
| 
      
 690 
     | 
    
         
            +
                  end
         
     | 
| 
      
 691 
     | 
    
         
            +
             
     | 
| 
      
 692 
     | 
    
         
            +
                  if options[:a]
         
     | 
| 
      
 693 
     | 
    
         
            +
                    wwid.move_item(new_entry, 'Archive', label: true)
         
     | 
| 
      
 694 
     | 
    
         
            +
                  end
         
     | 
| 
      
 695 
     | 
    
         
            +
             
     | 
| 
       474 
696 
     | 
    
         
             
                  wwid.write(wwid.doing_file)
         
     | 
| 
       475 
697 
     | 
    
         
             
                elsif args.empty? && $stdin.stat.size.zero?
         
     | 
| 
       476 
698 
     | 
    
         
             
                  if options[:r]
         
     | 
| 
       477 
699 
     | 
    
         
             
                    wwid.tag_last({ tags: ['done'], count: 1, section: section, remove: true })
         
     | 
| 
       478 
700 
     | 
    
         
             
                  else
         
     | 
| 
       479 
     | 
    
         
            -
                     
     | 
| 
      
 701 
     | 
    
         
            +
                    note = options[:note] ? Doing::Note.new(options[:note]) : nil
         
     | 
| 
      
 702 
     | 
    
         
            +
                    opt = {
         
     | 
| 
       480 
703 
     | 
    
         
             
                      archive: options[:a],
         
     | 
| 
       481 
704 
     | 
    
         
             
                      back: finish_date,
         
     | 
| 
       482 
705 
     | 
    
         
             
                      count: 1,
         
     | 
| 
       483 
706 
     | 
    
         
             
                      date: options[:date],
         
     | 
| 
      
 707 
     | 
    
         
            +
                      note: note,
         
     | 
| 
       484 
708 
     | 
    
         
             
                      section: section,
         
     | 
| 
       485 
     | 
    
         
            -
                       
     | 
| 
      
 709 
     | 
    
         
            +
                      tags: ['done'],
         
     | 
| 
      
 710 
     | 
    
         
            +
                      took: took == 0 ? nil : took,
         
     | 
| 
      
 711 
     | 
    
         
            +
                      unfinished: options[:unfinished]
         
     | 
| 
       486 
712 
     | 
    
         
             
                    }
         
     | 
| 
       487 
     | 
    
         
            -
                    wwid.tag_last( 
     | 
| 
      
 713 
     | 
    
         
            +
                    wwid.tag_last(opt)
         
     | 
| 
       488 
714 
     | 
    
         
             
                  end
         
     | 
| 
       489 
715 
     | 
    
         
             
                elsif !args.empty?
         
     | 
| 
       490 
     | 
    
         
            -
                   
     | 
| 
      
 716 
     | 
    
         
            +
                  note = Doing::Note.new(options[:note])
         
     | 
| 
      
 717 
     | 
    
         
            +
                  title, new_note = wwid.format_input([args.join(' '), note.to_s].join("\n"))
         
     | 
| 
       491 
718 
     | 
    
         
             
                  title.chomp!
         
     | 
| 
       492 
     | 
    
         
            -
                  title += " @done#{donedate}"
         
     | 
| 
       493 
719 
     | 
    
         
             
                  section = 'Archive' if options[:a]
         
     | 
| 
       494 
     | 
    
         
            -
                   
     | 
| 
      
 720 
     | 
    
         
            +
                  new_entry = Doing::Item.new(date, title, section, new_note)
         
     | 
| 
      
 721 
     | 
    
         
            +
                  if new_entry.should_finish?
         
     | 
| 
      
 722 
     | 
    
         
            +
                    if new_entry.should_time?
         
     | 
| 
      
 723 
     | 
    
         
            +
                      new_entry.tag('done', value: donedate)
         
     | 
| 
      
 724 
     | 
    
         
            +
                    else
         
     | 
| 
      
 725 
     | 
    
         
            +
                      new_entry.tag('done')
         
     | 
| 
      
 726 
     | 
    
         
            +
                    end
         
     | 
| 
      
 727 
     | 
    
         
            +
                  end
         
     | 
| 
      
 728 
     | 
    
         
            +
                  wwid.content[section][:items].push(new_entry)
         
     | 
| 
       495 
729 
     | 
    
         
             
                  wwid.write(wwid.doing_file)
         
     | 
| 
      
 730 
     | 
    
         
            +
                  Doing.logger.info('Entry Added:', new_entry.title)
         
     | 
| 
       496 
731 
     | 
    
         
             
                elsif $stdin.stat.size.positive?
         
     | 
| 
       497 
732 
     | 
    
         
             
                  title, note = wwid.format_input($stdin.read)
         
     | 
| 
       498 
     | 
    
         
            -
                   
     | 
| 
      
 733 
     | 
    
         
            +
                  note.add(options[:note]) if options[:note]
         
     | 
| 
       499 
734 
     | 
    
         
             
                  section = options[:a] ? 'Archive' : section
         
     | 
| 
       500 
     | 
    
         
            -
                   
     | 
| 
      
 735 
     | 
    
         
            +
                  new_entry = Doing::Item.new(date, title, section, note)
         
     | 
| 
      
 736 
     | 
    
         
            +
             
     | 
| 
      
 737 
     | 
    
         
            +
                  if new_entry.should_finish?
         
     | 
| 
      
 738 
     | 
    
         
            +
                    if new_entry.should_time?
         
     | 
| 
      
 739 
     | 
    
         
            +
                      new_entry.tag('done', value: donedate)
         
     | 
| 
      
 740 
     | 
    
         
            +
                    else
         
     | 
| 
      
 741 
     | 
    
         
            +
                      new_entry.tag('done')
         
     | 
| 
      
 742 
     | 
    
         
            +
                    end
         
     | 
| 
      
 743 
     | 
    
         
            +
                  end
         
     | 
| 
      
 744 
     | 
    
         
            +
             
     | 
| 
      
 745 
     | 
    
         
            +
                  wwid.content[section][:items].push(new_entry)
         
     | 
| 
       501 
746 
     | 
    
         
             
                  wwid.write(wwid.doing_file)
         
     | 
| 
      
 747 
     | 
    
         
            +
                  Doing.logger.info('Entry Added:', new_entry.title)
         
     | 
| 
       502 
748 
     | 
    
         
             
                else
         
     | 
| 
       503 
     | 
    
         
            -
                   
     | 
| 
      
 749 
     | 
    
         
            +
                  raise Doing::Errors::EmptyInput, 'You must provide content when creating a new entry'
         
     | 
| 
       504 
750 
     | 
    
         
             
                end
         
     | 
| 
       505 
751 
     | 
    
         
             
              end
         
     | 
| 
       506 
752 
     | 
    
         
             
            end
         
     | 
| 
         @@ -522,39 +768,37 @@ command :cancel do |c| 
     | 
|
| 
       522 
768 
     | 
    
         | 
| 
       523 
769 
     | 
    
         
             
              c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
         
     | 
| 
       524 
770 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       525 
     | 
    
         
            -
              c.flag [:bool], must_match:  
     | 
| 
      
 771 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
       526 
772 
     | 
    
         | 
| 
       527 
773 
     | 
    
         
             
              c.desc 'Cancel last entry (or entries) not already marked @done'
         
     | 
| 
       528 
774 
     | 
    
         
             
              c.switch %i[u unfinished], negatable: false, default_value: false
         
     | 
| 
       529 
775 
     | 
    
         | 
| 
      
 776 
     | 
    
         
            +
              c.desc 'Select item(s) to cancel from a menu of matching entries'
         
     | 
| 
      
 777 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
      
 778 
     | 
    
         
            +
             
     | 
| 
       530 
779 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       531 
780 
     | 
    
         
             
                if options[:section]
         
     | 
| 
       532 
781 
     | 
    
         
             
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
       533 
782 
     | 
    
         
             
                else
         
     | 
| 
       534 
     | 
    
         
            -
                  section =  
     | 
| 
      
 783 
     | 
    
         
            +
                  section = settings['current_section']
         
     | 
| 
       535 
784 
     | 
    
         
             
                end
         
     | 
| 
       536 
785 
     | 
    
         | 
| 
       537 
786 
     | 
    
         
             
                if options[:tag].nil?
         
     | 
| 
       538 
787 
     | 
    
         
             
                  tags = []
         
     | 
| 
       539 
788 
     | 
    
         
             
                else
         
     | 
| 
       540 
     | 
    
         
            -
                  tags = options[:tag]. 
     | 
| 
       541 
     | 
    
         
            -
                  options[:bool] = case options[:bool]
         
     | 
| 
       542 
     | 
    
         
            -
                                   when /(and|all)/i
         
     | 
| 
       543 
     | 
    
         
            -
                                    'AND'
         
     | 
| 
       544 
     | 
    
         
            -
                                   when /(any|or)/i
         
     | 
| 
       545 
     | 
    
         
            -
                                    'OR'
         
     | 
| 
       546 
     | 
    
         
            -
                                   when /(not|none)/i
         
     | 
| 
       547 
     | 
    
         
            -
                                    'NOT'
         
     | 
| 
       548 
     | 
    
         
            -
                                   else
         
     | 
| 
       549 
     | 
    
         
            -
                                    'AND'
         
     | 
| 
       550 
     | 
    
         
            -
                                   end
         
     | 
| 
      
 789 
     | 
    
         
            +
                  tags = options[:tag].to_tags
         
     | 
| 
       551 
790 
     | 
    
         
             
                end
         
     | 
| 
       552 
791 
     | 
    
         | 
| 
       553 
     | 
    
         
            -
                 
     | 
| 
      
 792 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, 'Only one argument allowed' if args.length > 1
         
     | 
| 
       554 
793 
     | 
    
         | 
| 
       555 
     | 
    
         
            -
                 
     | 
| 
      
 794 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, 'Invalid argument (specify number of recent items to mark @done)' unless args.empty? || args[0] =~ /\d+/
         
     | 
| 
      
 795 
     | 
    
         
            +
             
     | 
| 
      
 796 
     | 
    
         
            +
                if options[:interactive]
         
     | 
| 
      
 797 
     | 
    
         
            +
                  count = 0
         
     | 
| 
      
 798 
     | 
    
         
            +
                else
         
     | 
| 
      
 799 
     | 
    
         
            +
                  count = args[0] ? args[0].to_i : 1
         
     | 
| 
      
 800 
     | 
    
         
            +
                end
         
     | 
| 
       556 
801 
     | 
    
         | 
| 
       557 
     | 
    
         
            -
                count = args[0] ? args[0].to_i : 1
         
     | 
| 
       558 
802 
     | 
    
         
             
                opts = {
         
     | 
| 
       559 
803 
     | 
    
         
             
                  archive: options[:a],
         
     | 
| 
       560 
804 
     | 
    
         
             
                  count: count,
         
     | 
| 
         @@ -562,10 +806,12 @@ command :cancel do |c| 
     | 
|
| 
       562 
806 
     | 
    
         
             
                  section: section,
         
     | 
| 
       563 
807 
     | 
    
         
             
                  sequential: false,
         
     | 
| 
       564 
808 
     | 
    
         
             
                  tag: tags,
         
     | 
| 
       565 
     | 
    
         
            -
                  tag_bool: options[:bool],
         
     | 
| 
      
 809 
     | 
    
         
            +
                  tag_bool: options[:bool].normalize_bool,
         
     | 
| 
       566 
810 
     | 
    
         
             
                  tags: ['done'],
         
     | 
| 
       567 
     | 
    
         
            -
                  unfinished: options[:unfinished]
         
     | 
| 
      
 811 
     | 
    
         
            +
                  unfinished: options[:unfinished],
         
     | 
| 
      
 812 
     | 
    
         
            +
                  interactive: options[:interactive]
         
     | 
| 
       568 
813 
     | 
    
         
             
                }
         
     | 
| 
      
 814 
     | 
    
         
            +
             
     | 
| 
       569 
815 
     | 
    
         
             
                wwid.tag_last(opts)
         
     | 
| 
       570 
816 
     | 
    
         
             
              end
         
     | 
| 
       571 
817 
     | 
    
         
             
            end
         
     | 
| 
         @@ -594,13 +840,16 @@ command :finish do |c| 
     | 
|
| 
       594 
840 
     | 
    
         
             
              c.arg_name 'TAG'
         
     | 
| 
       595 
841 
     | 
    
         
             
              c.flag [:tag]
         
     | 
| 
       596 
842 
     | 
    
         | 
| 
       597 
     | 
    
         
            -
              c.desc 'Finish the last X entries matching search filter, surround with slashes for regex (e.g. "/query.*/")'
         
     | 
| 
      
 843 
     | 
    
         
            +
              c.desc 'Finish the last X entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
         
     | 
| 
       598 
844 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
       599 
845 
     | 
    
         
             
              c.flag [:search]
         
     | 
| 
       600 
846 
     | 
    
         | 
| 
       601 
847 
     | 
    
         
             
              c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
         
     | 
| 
       602 
848 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       603 
     | 
    
         
            -
              c.flag [:bool], must_match:  
     | 
| 
      
 849 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
      
 850 
     | 
    
         
            +
             
     | 
| 
      
 851 
     | 
    
         
            +
              c.desc 'Remove done tag'
         
     | 
| 
      
 852 
     | 
    
         
            +
              c.switch %i[r remove], negatable: false, default_value: false
         
     | 
| 
       604 
853 
     | 
    
         | 
| 
       605 
854 
     | 
    
         
             
              c.desc 'Finish last entry (or entries) not already marked @done'
         
     | 
| 
       606 
855 
     | 
    
         
             
              c.switch %i[u unfinished], negatable: false, default_value: false
         
     | 
| 
         @@ -617,32 +866,29 @@ command :finish do |c| 
     | 
|
| 
       617 
866 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       618 
867 
     | 
    
         
             
              c.flag %i[s section]
         
     | 
| 
       619 
868 
     | 
    
         | 
| 
       620 
     | 
    
         
            -
              c. 
     | 
| 
       621 
     | 
    
         
            -
             
     | 
| 
       622 
     | 
    
         
            -
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
       623 
     | 
    
         
            -
                else
         
     | 
| 
       624 
     | 
    
         
            -
                  section = wwid.config['current_section']
         
     | 
| 
       625 
     | 
    
         
            -
                end
         
     | 
| 
      
 869 
     | 
    
         
            +
              c.desc 'Select item(s) to finish from a menu of matching entries'
         
     | 
| 
      
 870 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
       626 
871 
     | 
    
         | 
| 
      
 872 
     | 
    
         
            +
              c.action do |_global_options, options, args|
         
     | 
| 
       627 
873 
     | 
    
         
             
                unless options[:auto]
         
     | 
| 
       628 
874 
     | 
    
         
             
                  if options[:took]
         
     | 
| 
       629 
875 
     | 
    
         
             
                    took = wwid.chronify_qty(options[:took])
         
     | 
| 
       630 
     | 
    
         
            -
                     
     | 
| 
      
 876 
     | 
    
         
            +
                    raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --took' if took.nil?
         
     | 
| 
       631 
877 
     | 
    
         
             
                  end
         
     | 
| 
       632 
878 
     | 
    
         | 
| 
       633 
     | 
    
         
            -
                   
     | 
| 
      
 879 
     | 
    
         
            +
                  raise Doing::Errors::InvalidArgument, '--back and --took can not be used together' if options[:back] && options[:took]
         
     | 
| 
       634 
880 
     | 
    
         | 
| 
       635 
     | 
    
         
            -
                   
     | 
| 
      
 881 
     | 
    
         
            +
                  raise Doing::Errors::InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
         
     | 
| 
       636 
882 
     | 
    
         | 
| 
       637 
883 
     | 
    
         
             
                  if options[:at]
         
     | 
| 
       638 
     | 
    
         
            -
                    finish_date = wwid.chronify(options[:at])
         
     | 
| 
       639 
     | 
    
         
            -
                     
     | 
| 
      
 884 
     | 
    
         
            +
                    finish_date = wwid.chronify(options[:at], guess: :begin)
         
     | 
| 
      
 885 
     | 
    
         
            +
                    raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string for --at' if finish_date.nil?
         
     | 
| 
       640 
886 
     | 
    
         | 
| 
       641 
887 
     | 
    
         
             
                    date = options[:took] ? finish_date - took : finish_date
         
     | 
| 
       642 
888 
     | 
    
         
             
                  elsif options[:back]
         
     | 
| 
       643 
889 
     | 
    
         
             
                    date = wwid.chronify(options[:back])
         
     | 
| 
       644 
890 
     | 
    
         | 
| 
       645 
     | 
    
         
            -
                     
     | 
| 
      
 891 
     | 
    
         
            +
                    raise Doing::Errors::InvalidTimeExpression, 'Unable to parse date string' if date.nil?
         
     | 
| 
       646 
892 
     | 
    
         
             
                  elsif options[:took]
         
     | 
| 
       647 
893 
     | 
    
         
             
                    date = wwid.chronify_qty(options[:took])
         
     | 
| 
       648 
894 
     | 
    
         
             
                  else
         
     | 
| 
         @@ -653,44 +899,42 @@ command :finish do |c| 
     | 
|
| 
       653 
899 
     | 
    
         
             
                if options[:tag].nil?
         
     | 
| 
       654 
900 
     | 
    
         
             
                  tags = []
         
     | 
| 
       655 
901 
     | 
    
         
             
                else
         
     | 
| 
       656 
     | 
    
         
            -
                  tags = options[:tag]. 
     | 
| 
       657 
     | 
    
         
            -
                  options[:bool] = case options[:bool]
         
     | 
| 
       658 
     | 
    
         
            -
                                         when /(and|all)/i
         
     | 
| 
       659 
     | 
    
         
            -
                                          'AND'
         
     | 
| 
       660 
     | 
    
         
            -
                                         when /(any|or)/i
         
     | 
| 
       661 
     | 
    
         
            -
                                          'OR'
         
     | 
| 
       662 
     | 
    
         
            -
                                         when /(not|none)/i
         
     | 
| 
       663 
     | 
    
         
            -
                                          'NOT'
         
     | 
| 
       664 
     | 
    
         
            -
                                         else
         
     | 
| 
       665 
     | 
    
         
            -
                                          'AND'
         
     | 
| 
       666 
     | 
    
         
            -
                                         end
         
     | 
| 
      
 902 
     | 
    
         
            +
                  tags = options[:tag].to_tags
         
     | 
| 
       667 
903 
     | 
    
         
             
                end
         
     | 
| 
       668 
904 
     | 
    
         | 
| 
       669 
     | 
    
         
            -
                 
     | 
| 
      
 905 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, 'Only one argument allowed' if args.length > 1
         
     | 
| 
      
 906 
     | 
    
         
            +
             
     | 
| 
      
 907 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, 'Invalid argument (specify number of recent items to mark @done)' unless args.length == 0 || args[0] =~ /\d+/
         
     | 
| 
       670 
908 
     | 
    
         | 
| 
       671 
     | 
    
         
            -
                 
     | 
| 
      
 909 
     | 
    
         
            +
                if options[:interactive]
         
     | 
| 
      
 910 
     | 
    
         
            +
                  count = 0
         
     | 
| 
      
 911 
     | 
    
         
            +
                else
         
     | 
| 
      
 912 
     | 
    
         
            +
                  count = args[0] ? args[0].to_i : 1
         
     | 
| 
      
 913 
     | 
    
         
            +
                end
         
     | 
| 
       672 
914 
     | 
    
         | 
| 
       673 
     | 
    
         
            -
                count = args[0] ? args[0].to_i : 1
         
     | 
| 
       674 
915 
     | 
    
         
             
                opts = {
         
     | 
| 
       675 
     | 
    
         
            -
                  archive: options[: 
     | 
| 
      
 916 
     | 
    
         
            +
                  archive: options[:archive],
         
     | 
| 
       676 
917 
     | 
    
         
             
                  back: date,
         
     | 
| 
       677 
918 
     | 
    
         
             
                  count: count,
         
     | 
| 
       678 
919 
     | 
    
         
             
                  date: options[:date],
         
     | 
| 
       679 
920 
     | 
    
         
             
                  search: options[:search],
         
     | 
| 
       680 
     | 
    
         
            -
                  section: section,
         
     | 
| 
      
 921 
     | 
    
         
            +
                  section: options[:section],
         
     | 
| 
       681 
922 
     | 
    
         
             
                  sequential: options[:auto],
         
     | 
| 
       682 
923 
     | 
    
         
             
                  tag: tags,
         
     | 
| 
       683 
     | 
    
         
            -
                  tag_bool: options[:bool],
         
     | 
| 
      
 924 
     | 
    
         
            +
                  tag_bool: options[:bool].normalize_bool,
         
     | 
| 
       684 
925 
     | 
    
         
             
                  tags: ['done'],
         
     | 
| 
       685 
     | 
    
         
            -
                  unfinished: options[:unfinished]
         
     | 
| 
      
 926 
     | 
    
         
            +
                  unfinished: options[:unfinished],
         
     | 
| 
      
 927 
     | 
    
         
            +
                  remove: options[:remove],
         
     | 
| 
      
 928 
     | 
    
         
            +
                  interactive: options[:interactive]
         
     | 
| 
       686 
929 
     | 
    
         
             
                }
         
     | 
| 
      
 930 
     | 
    
         
            +
             
     | 
| 
       687 
931 
     | 
    
         
             
                wwid.tag_last(opts)
         
     | 
| 
       688 
932 
     | 
    
         
             
              end
         
     | 
| 
       689 
933 
     | 
    
         
             
            end
         
     | 
| 
       690 
934 
     | 
    
         | 
| 
       691 
935 
     | 
    
         
             
            desc 'Repeat last entry as new entry'
         
     | 
| 
       692 
     | 
    
         
            -
            command [ 
     | 
| 
       693 
     | 
    
         
            -
              c.desc ' 
     | 
| 
      
 936 
     | 
    
         
            +
            command %i[again resume] do |c|
         
     | 
| 
      
 937 
     | 
    
         
            +
              c.desc 'Get last entry from a specific section'
         
     | 
| 
       694 
938 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       695 
939 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
       696 
940 
     | 
    
         | 
| 
         @@ -703,45 +947,38 @@ command [:again, :resume] do |c| 
     | 
|
| 
       703 
947 
     | 
    
         
             
              c.flag [:tag]
         
     | 
| 
       704 
948 
     | 
    
         | 
| 
       705 
949 
     | 
    
         
             
              c.desc 'Repeat last entry matching search. Surround with
         
     | 
| 
       706 
     | 
    
         
            -
              slashes for regex (e.g. "/query/").'
         
     | 
| 
      
 950 
     | 
    
         
            +
              slashes for regex (e.g. "/query/"), start with a single quote for exact match ("\'query").'
         
     | 
| 
       707 
951 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
       708 
952 
     | 
    
         
             
              c.flag [:search]
         
     | 
| 
       709 
953 
     | 
    
         | 
| 
       710 
954 
     | 
    
         
             
              c.desc 'Boolean used to combine multiple tags'
         
     | 
| 
       711 
955 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       712 
     | 
    
         
            -
              c.flag [:bool], must_match:  
     | 
| 
      
 956 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
      
 957 
     | 
    
         
            +
             
     | 
| 
      
 958 
     | 
    
         
            +
              c.desc "Edit duplicated entry with #{Doing::Util.default_editor} before adding"
         
     | 
| 
      
 959 
     | 
    
         
            +
              c.switch %i[e editor], negatable: false, default_value: false
         
     | 
| 
       713 
960 
     | 
    
         | 
| 
       714 
961 
     | 
    
         
             
              c.desc 'Note'
         
     | 
| 
       715 
962 
     | 
    
         
             
              c.arg_name 'TEXT'
         
     | 
| 
       716 
963 
     | 
    
         
             
              c.flag %i[n note]
         
     | 
| 
       717 
964 
     | 
    
         | 
| 
      
 965 
     | 
    
         
            +
              c.desc 'Select item to resume from a menu of matching entries'
         
     | 
| 
      
 966 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
      
 967 
     | 
    
         
            +
             
     | 
| 
       718 
968 
     | 
    
         
             
              c.action do |_global_options, options, _args|
         
     | 
| 
       719 
     | 
    
         
            -
                tags = options[:tag].nil? ? [] : options[:tag]. 
     | 
| 
       720 
     | 
    
         
            -
                 
     | 
| 
       721 
     | 
    
         
            -
             
     | 
| 
       722 
     | 
    
         
            -
             
     | 
| 
       723 
     | 
    
         
            -
             
     | 
| 
       724 
     | 
    
         
            -
             
     | 
| 
       725 
     | 
    
         
            -
             
     | 
| 
       726 
     | 
    
         
            -
                                  'NOT'
         
     | 
| 
       727 
     | 
    
         
            -
                                 else
         
     | 
| 
       728 
     | 
    
         
            -
                                  'AND'
         
     | 
| 
       729 
     | 
    
         
            -
                                 end
         
     | 
| 
       730 
     | 
    
         
            -
                opts = {
         
     | 
| 
       731 
     | 
    
         
            -
                  in: options[:in],
         
     | 
| 
       732 
     | 
    
         
            -
                  note: options[:n],
         
     | 
| 
       733 
     | 
    
         
            -
                  search: options[:search],
         
     | 
| 
       734 
     | 
    
         
            -
                  section: options[:s],
         
     | 
| 
       735 
     | 
    
         
            -
                  tag: tags,
         
     | 
| 
       736 
     | 
    
         
            -
                  tag_bool: options[:bool]
         
     | 
| 
       737 
     | 
    
         
            -
                }
         
     | 
| 
       738 
     | 
    
         
            -
                wwid.restart_last(opts)
         
     | 
| 
      
 969 
     | 
    
         
            +
                tags = options[:tag].nil? ? [] : options[:tag].to_tags
         
     | 
| 
      
 970 
     | 
    
         
            +
                opts = options
         
     | 
| 
      
 971 
     | 
    
         
            +
                opts[:tag] = tags
         
     | 
| 
      
 972 
     | 
    
         
            +
                opts[:tag_bool] = options[:bool].normalize_bool
         
     | 
| 
      
 973 
     | 
    
         
            +
                opts[:interactive] = options[:interactive]
         
     | 
| 
      
 974 
     | 
    
         
            +
             
     | 
| 
      
 975 
     | 
    
         
            +
                wwid.repeat_last(opts)
         
     | 
| 
       739 
976 
     | 
    
         
             
              end
         
     | 
| 
       740 
977 
     | 
    
         
             
            end
         
     | 
| 
       741 
978 
     | 
    
         | 
| 
       742 
979 
     | 
    
         
             
            desc 'Add tag(s) to last entry'
         
     | 
| 
       743 
980 
     | 
    
         
             
            long_desc 'Add (or remove) tags from the last entry, or from multiple entries
         
     | 
| 
       744 
     | 
    
         
            -
                      (with `--count`), entries matching a search (with `--search), or entries
         
     | 
| 
      
 981 
     | 
    
         
            +
                      (with `--count`), entries matching a search (with `--search`), or entries
         
     | 
| 
       745 
982 
     | 
    
         
             
                      containing another tag (with `--tag`).
         
     | 
| 
       746 
983 
     | 
    
         | 
| 
       747 
984 
     | 
    
         
             
                      When removing tags with `-r`, wildcards are allowed (`*` to match
         
     | 
| 
         @@ -755,13 +992,19 @@ long_desc 'Add (or remove) tags from the last entry, or from multiple entries 
     | 
|
| 
       755 
992 
     | 
    
         
             
                      Tag name arguments do not need to be prefixed with @.'
         
     | 
| 
       756 
993 
     | 
    
         
             
            arg_name 'TAG', :multiple
         
     | 
| 
       757 
994 
     | 
    
         
             
            command :tag do |c|
         
     | 
| 
      
 995 
     | 
    
         
            +
              c.example 'doing tag mytag', desc: 'Add @mytag to the last entry created'
         
     | 
| 
      
 996 
     | 
    
         
            +
              c.example 'doing tag --remove mytag', desc: 'Remove @mytag from the last entry created'
         
     | 
| 
      
 997 
     | 
    
         
            +
              c.example 'doing tag --rename "other*" --count 10 newtag', desc: 'Rename tags beginning with "other" (wildcard) to @newtag on the last 10 entries'
         
     | 
| 
      
 998 
     | 
    
         
            +
              c.example 'doing tag --search "developing" coding', desc: 'Add @coding to the last entry containing string "developing" (fuzzy matching)'
         
     | 
| 
      
 999 
     | 
    
         
            +
              c.example 'doing tag --interactive --tag project1 coding', desc: 'Create an interactive menu from entries tagged @project1, selection(s) will be tagged with @coding'
         
     | 
| 
      
 1000 
     | 
    
         
            +
             
     | 
| 
       758 
1001 
     | 
    
         
             
              c.desc 'Section'
         
     | 
| 
       759 
1002 
     | 
    
         
             
              c.arg_name 'SECTION_NAME'
         
     | 
| 
       760 
1003 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
       761 
1004 
     | 
    
         | 
| 
       762 
1005 
     | 
    
         
             
              c.desc 'How many recent entries to tag (0 for all)'
         
     | 
| 
       763 
1006 
     | 
    
         
             
              c.arg_name 'COUNT'
         
     | 
| 
       764 
     | 
    
         
            -
              c.flag %i[c count], default_value: 1
         
     | 
| 
      
 1007 
     | 
    
         
            +
              c.flag %i[c count], default_value: 1, must_match: /^\d+$/, type: Integer
         
     | 
| 
       765 
1008 
     | 
    
         | 
| 
       766 
1009 
     | 
    
         
             
              c.desc 'Replace existing tag with tag argument, wildcards (*,?) allowed, or use with --regex'
         
     | 
| 
       767 
1010 
     | 
    
         
             
              c.arg_name 'ORIG_TAG'
         
     | 
| 
         @@ -790,18 +1033,21 @@ command :tag do |c| 
     | 
|
| 
       790 
1033 
     | 
    
         
             
              c.arg_name 'TAG'
         
     | 
| 
       791 
1034 
     | 
    
         
             
              c.flag [:tag]
         
     | 
| 
       792 
1035 
     | 
    
         | 
| 
       793 
     | 
    
         
            -
              c.desc 'Tag entries matching search filter, surround with slashes for regex (e.g. "/query.*/")'
         
     | 
| 
      
 1036 
     | 
    
         
            +
              c.desc 'Tag entries matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
         
     | 
| 
       794 
1037 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
       795 
1038 
     | 
    
         
             
              c.flag [:search]
         
     | 
| 
       796 
1039 
     | 
    
         | 
| 
       797 
1040 
     | 
    
         
             
              c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
         
     | 
| 
       798 
1041 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       799 
     | 
    
         
            -
              c.flag [:bool], must_match:  
     | 
| 
      
 1042 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
      
 1043 
     | 
    
         
            +
             
     | 
| 
      
 1044 
     | 
    
         
            +
              c.desc 'Select item(s) to tag from a menu of matching entries'
         
     | 
| 
      
 1045 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
       800 
1046 
     | 
    
         | 
| 
       801 
1047 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       802 
     | 
    
         
            -
                 
     | 
| 
      
 1048 
     | 
    
         
            +
                raise Doing::Errors::MissingArgument, 'You must specify at least one tag' if args.empty? && !options[:a]
         
     | 
| 
       803 
1049 
     | 
    
         | 
| 
       804 
     | 
    
         
            -
                 
     | 
| 
      
 1050 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
         
     | 
| 
       805 
1051 
     | 
    
         | 
| 
       806 
1052 
     | 
    
         
             
                section = 'All'
         
     | 
| 
       807 
1053 
     | 
    
         | 
| 
         @@ -813,17 +1059,7 @@ command :tag do |c| 
     | 
|
| 
       813 
1059 
     | 
    
         
             
                if options[:tag].nil?
         
     | 
| 
       814 
1060 
     | 
    
         
             
                  search_tags = []
         
     | 
| 
       815 
1061 
     | 
    
         
             
                else
         
     | 
| 
       816 
     | 
    
         
            -
                  search_tags = options[:tag]. 
     | 
| 
       817 
     | 
    
         
            -
                  options[:bool] = case options[:bool]
         
     | 
| 
       818 
     | 
    
         
            -
                                         when /(and|all)/i
         
     | 
| 
       819 
     | 
    
         
            -
                                          'AND'
         
     | 
| 
       820 
     | 
    
         
            -
                                         when /(any|or)/i
         
     | 
| 
       821 
     | 
    
         
            -
                                          'OR'
         
     | 
| 
       822 
     | 
    
         
            -
                                         when /(not|none)/i
         
     | 
| 
       823 
     | 
    
         
            -
                                          'NOT'
         
     | 
| 
       824 
     | 
    
         
            -
                                         else
         
     | 
| 
       825 
     | 
    
         
            -
                                          'AND'
         
     | 
| 
       826 
     | 
    
         
            -
                                         end
         
     | 
| 
      
 1062 
     | 
    
         
            +
                  search_tags = options[:tag].to_tags
         
     | 
| 
       827 
1063 
     | 
    
         
             
                end
         
     | 
| 
       828 
1064 
     | 
    
         | 
| 
       829 
1065 
     | 
    
         
             
                if options[:autotag]
         
     | 
| 
         @@ -838,7 +1074,13 @@ command :tag do |c| 
     | 
|
| 
       838 
1074 
     | 
    
         
             
                  tags.map! { |tag| tag.sub(/^@/, '').strip }
         
     | 
| 
       839 
1075 
     | 
    
         
             
                end
         
     | 
| 
       840 
1076 
     | 
    
         | 
| 
       841 
     | 
    
         
            -
                 
     | 
| 
      
 1077 
     | 
    
         
            +
                if options[:interactive]
         
     | 
| 
      
 1078 
     | 
    
         
            +
                  count = 0
         
     | 
| 
      
 1079 
     | 
    
         
            +
                  options[:force] = true
         
     | 
| 
      
 1080 
     | 
    
         
            +
                else
         
     | 
| 
      
 1081 
     | 
    
         
            +
                  count = options[:count].to_i
         
     | 
| 
      
 1082 
     | 
    
         
            +
                end
         
     | 
| 
      
 1083 
     | 
    
         
            +
             
     | 
| 
       842 
1084 
     | 
    
         | 
| 
       843 
1085 
     | 
    
         
             
                if count.zero? && !options[:force]
         
     | 
| 
       844 
1086 
     | 
    
         
             
                  if options[:search]
         
     | 
| 
         @@ -869,45 +1111,121 @@ command :tag do |c| 
     | 
|
| 
       869 
1111 
     | 
    
         
             
                options[:section] = section
         
     | 
| 
       870 
1112 
     | 
    
         
             
                options[:tag] = search_tags
         
     | 
| 
       871 
1113 
     | 
    
         
             
                options[:tags] = tags
         
     | 
| 
       872 
     | 
    
         
            -
                options[:tag_bool] = options[:bool]
         
     | 
| 
       873 
     | 
    
         
            -
             
     | 
| 
       874 
     | 
    
         
            -
                # opts = {
         
     | 
| 
       875 
     | 
    
         
            -
                #   autotag: options[:a],
         
     | 
| 
       876 
     | 
    
         
            -
                #   count: count,
         
     | 
| 
       877 
     | 
    
         
            -
                #   date: options[:date],
         
     | 
| 
       878 
     | 
    
         
            -
                #   iregex: options[:iregex]
         
     | 
| 
       879 
     | 
    
         
            -
                #   remove: options[:r],
         
     | 
| 
       880 
     | 
    
         
            -
                #   search: options[:search],
         
     | 
| 
       881 
     | 
    
         
            -
                #   section: section,
         
     | 
| 
       882 
     | 
    
         
            -
                #   tag: search_tags,
         
     | 
| 
       883 
     | 
    
         
            -
                #   tag_bool: options[:bool],
         
     | 
| 
       884 
     | 
    
         
            -
                #   tags: tags,
         
     | 
| 
       885 
     | 
    
         
            -
                #   unfinished: options[:unfinished]
         
     | 
| 
       886 
     | 
    
         
            -
                # }
         
     | 
| 
      
 1114 
     | 
    
         
            +
                options[:tag_bool] = options[:bool].normalize_bool
         
     | 
| 
      
 1115 
     | 
    
         
            +
             
     | 
| 
       887 
1116 
     | 
    
         
             
                wwid.tag_last(options)
         
     | 
| 
       888 
1117 
     | 
    
         
             
              end
         
     | 
| 
       889 
1118 
     | 
    
         
             
            end
         
     | 
| 
       890 
1119 
     | 
    
         | 
| 
       891 
     | 
    
         
            -
            desc ' 
     | 
| 
      
 1120 
     | 
    
         
            +
            # desc 'Autotag last X entries'
         
     | 
| 
      
 1121 
     | 
    
         
            +
            # arg_name 'COUNT'
         
     | 
| 
      
 1122 
     | 
    
         
            +
            # command :autotag do |c|
         
     | 
| 
      
 1123 
     | 
    
         
            +
            #   c.action do |global_options, options, args|
         
     | 
| 
      
 1124 
     | 
    
         
            +
            #     options = {
         
     | 
| 
      
 1125 
     | 
    
         
            +
            #       autotag: true,
         
     | 
| 
      
 1126 
     | 
    
         
            +
            #       count: args[0].to_i
         
     | 
| 
      
 1127 
     | 
    
         
            +
            #     }
         
     | 
| 
      
 1128 
     | 
    
         
            +
            #     cmd = commands[:tag]
         
     | 
| 
      
 1129 
     | 
    
         
            +
            #     cmd.action.(global_options, options, [])
         
     | 
| 
      
 1130 
     | 
    
         
            +
            #   end
         
     | 
| 
      
 1131 
     | 
    
         
            +
            # end
         
     | 
| 
      
 1132 
     | 
    
         
            +
             
     | 
| 
      
 1133 
     | 
    
         
            +
            desc 'Mark last entry as flagged'
         
     | 
| 
       892 
1134 
     | 
    
         
             
            command [:mark, :flag] do |c|
         
     | 
| 
      
 1135 
     | 
    
         
            +
              c.example 'doing flag', desc: 'Add @flagged to the last entry created'
         
     | 
| 
      
 1136 
     | 
    
         
            +
              c.example 'doing flag --tag project1 --count 2', desc: 'Add @flagged to the last 2 entries tagged @project1'
         
     | 
| 
      
 1137 
     | 
    
         
            +
              c.example 'doing flag --interactive --search "/(develop|cod)ing/"', desc: 'Find entries matching regular expression and create a menu allowing multiple selections, selected items will be @flagged'
         
     | 
| 
      
 1138 
     | 
    
         
            +
             
     | 
| 
       893 
1139 
     | 
    
         
             
              c.desc 'Section'
         
     | 
| 
       894 
     | 
    
         
            -
              c.arg_name ' 
     | 
| 
       895 
     | 
    
         
            -
              c.flag %i[s section]
         
     | 
| 
      
 1140 
     | 
    
         
            +
              c.arg_name 'SECTION_NAME'
         
     | 
| 
      
 1141 
     | 
    
         
            +
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
      
 1142 
     | 
    
         
            +
             
     | 
| 
      
 1143 
     | 
    
         
            +
              c.desc 'How many recent entries to tag (0 for all)'
         
     | 
| 
      
 1144 
     | 
    
         
            +
              c.arg_name 'COUNT'
         
     | 
| 
      
 1145 
     | 
    
         
            +
              c.flag %i[c count], default_value: 1, must_match: /^\d+$/, type: Integer
         
     | 
| 
      
 1146 
     | 
    
         
            +
             
     | 
| 
      
 1147 
     | 
    
         
            +
              c.desc 'Don\'t ask permission to flag all entries when count is 0'
         
     | 
| 
      
 1148 
     | 
    
         
            +
              c.switch %i[force], negatable: false, default_value: false
         
     | 
| 
       896 
1149 
     | 
    
         | 
| 
       897 
     | 
    
         
            -
              c.desc ' 
     | 
| 
      
 1150 
     | 
    
         
            +
              c.desc 'Include current date/time with tag'
         
     | 
| 
      
 1151 
     | 
    
         
            +
              c.switch %i[d date], negatable: false, default_value: false
         
     | 
| 
      
 1152 
     | 
    
         
            +
             
     | 
| 
      
 1153 
     | 
    
         
            +
              c.desc 'Remove flag'
         
     | 
| 
       898 
1154 
     | 
    
         
             
              c.switch %i[r remove], negatable: false, default_value: false
         
     | 
| 
       899 
1155 
     | 
    
         | 
| 
       900 
     | 
    
         
            -
              c.desc ' 
     | 
| 
      
 1156 
     | 
    
         
            +
              c.desc 'Flag last entry (or entries) not marked @done'
         
     | 
| 
       901 
1157 
     | 
    
         
             
              c.switch %i[u unfinished], negatable: false, default_value: false
         
     | 
| 
       902 
1158 
     | 
    
         | 
| 
      
 1159 
     | 
    
         
            +
              c.desc 'Flag the last entry containing TAG.
         
     | 
| 
      
 1160 
     | 
    
         
            +
              Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
         
     | 
| 
      
 1161 
     | 
    
         
            +
              c.arg_name 'TAG'
         
     | 
| 
      
 1162 
     | 
    
         
            +
              c.flag [:tag]
         
     | 
| 
      
 1163 
     | 
    
         
            +
             
     | 
| 
      
 1164 
     | 
    
         
            +
              c.desc 'Flag the last entry matching search filter, surround with slashes for regex (e.g. "/query.*/"), start with single quote for exact match ("\'query")'
         
     | 
| 
      
 1165 
     | 
    
         
            +
              c.arg_name 'QUERY'
         
     | 
| 
      
 1166 
     | 
    
         
            +
              c.flag [:search]
         
     | 
| 
      
 1167 
     | 
    
         
            +
             
     | 
| 
      
 1168 
     | 
    
         
            +
              c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
         
     | 
| 
      
 1169 
     | 
    
         
            +
              c.arg_name 'BOOLEAN'
         
     | 
| 
      
 1170 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
      
 1171 
     | 
    
         
            +
             
     | 
| 
      
 1172 
     | 
    
         
            +
              c.desc 'Select item(s) to flag from a menu of matching entries'
         
     | 
| 
      
 1173 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
      
 1174 
     | 
    
         
            +
             
     | 
| 
       903 
1175 
     | 
    
         
             
              c.action do |_global_options, options, _args|
         
     | 
| 
       904 
     | 
    
         
            -
                mark =  
     | 
| 
       905 
     | 
    
         
            -
             
     | 
| 
       906 
     | 
    
         
            -
             
     | 
| 
       907 
     | 
    
         
            -
             
     | 
| 
       908 
     | 
    
         
            -
             
     | 
| 
       909 
     | 
    
         
            -
             
     | 
| 
       910 
     | 
    
         
            -
                 
     | 
| 
      
 1176 
     | 
    
         
            +
                mark = settings['marker_tag'] || 'flagged'
         
     | 
| 
      
 1177 
     | 
    
         
            +
             
     | 
| 
      
 1178 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, '--search and --tag can not be used together' if options[:search] && options[:tag]
         
     | 
| 
      
 1179 
     | 
    
         
            +
             
     | 
| 
      
 1180 
     | 
    
         
            +
                section = 'All'
         
     | 
| 
      
 1181 
     | 
    
         
            +
             
     | 
| 
      
 1182 
     | 
    
         
            +
                if options[:section]
         
     | 
| 
      
 1183 
     | 
    
         
            +
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
      
 1184 
     | 
    
         
            +
                end
         
     | 
| 
      
 1185 
     | 
    
         
            +
             
     | 
| 
      
 1186 
     | 
    
         
            +
                if options[:tag].nil?
         
     | 
| 
      
 1187 
     | 
    
         
            +
                  search_tags = []
         
     | 
| 
      
 1188 
     | 
    
         
            +
                else
         
     | 
| 
      
 1189 
     | 
    
         
            +
                  search_tags = options[:tag].to_tags
         
     | 
| 
      
 1190 
     | 
    
         
            +
                end
         
     | 
| 
      
 1191 
     | 
    
         
            +
             
     | 
| 
      
 1192 
     | 
    
         
            +
                if options[:interactive]
         
     | 
| 
      
 1193 
     | 
    
         
            +
                  count = 0
         
     | 
| 
      
 1194 
     | 
    
         
            +
                  options[:force] = true
         
     | 
| 
      
 1195 
     | 
    
         
            +
                else
         
     | 
| 
      
 1196 
     | 
    
         
            +
                  count = options[:count].to_i
         
     | 
| 
      
 1197 
     | 
    
         
            +
                end
         
     | 
| 
      
 1198 
     | 
    
         
            +
             
     | 
| 
      
 1199 
     | 
    
         
            +
                if count.zero? && !options[:force]
         
     | 
| 
      
 1200 
     | 
    
         
            +
                  if options[:search]
         
     | 
| 
      
 1201 
     | 
    
         
            +
                    section_q = ' matching your search terms'
         
     | 
| 
      
 1202 
     | 
    
         
            +
                  elsif options[:tag]
         
     | 
| 
      
 1203 
     | 
    
         
            +
                    section_q = ' matching your tag search'
         
     | 
| 
      
 1204 
     | 
    
         
            +
                  elsif section == 'All'
         
     | 
| 
      
 1205 
     | 
    
         
            +
                    section_q = ''
         
     | 
| 
      
 1206 
     | 
    
         
            +
                  else
         
     | 
| 
      
 1207 
     | 
    
         
            +
                    section_q = " in section #{section}"
         
     | 
| 
      
 1208 
     | 
    
         
            +
                  end
         
     | 
| 
      
 1209 
     | 
    
         
            +
             
     | 
| 
      
 1210 
     | 
    
         
            +
             
     | 
| 
      
 1211 
     | 
    
         
            +
                  question = if options[:remove]
         
     | 
| 
      
 1212 
     | 
    
         
            +
                               "Are you sure you want to unflag all entries#{section_q}"
         
     | 
| 
      
 1213 
     | 
    
         
            +
                             else
         
     | 
| 
      
 1214 
     | 
    
         
            +
                               "Are you sure you want to flag all records#{section_q}"
         
     | 
| 
      
 1215 
     | 
    
         
            +
                             end
         
     | 
| 
      
 1216 
     | 
    
         
            +
             
     | 
| 
      
 1217 
     | 
    
         
            +
                  res = wwid.yn(question, default_response: false)
         
     | 
| 
      
 1218 
     | 
    
         
            +
             
     | 
| 
      
 1219 
     | 
    
         
            +
                  exit_now! 'Cancelled' unless res
         
     | 
| 
      
 1220 
     | 
    
         
            +
                end
         
     | 
| 
      
 1221 
     | 
    
         
            +
             
     | 
| 
      
 1222 
     | 
    
         
            +
                options[:count] = count
         
     | 
| 
      
 1223 
     | 
    
         
            +
                options[:section] = section
         
     | 
| 
      
 1224 
     | 
    
         
            +
                options[:tag] = search_tags
         
     | 
| 
      
 1225 
     | 
    
         
            +
                options[:tags] = [mark]
         
     | 
| 
      
 1226 
     | 
    
         
            +
                options[:tag_bool] = options[:bool].normalize_bool
         
     | 
| 
      
 1227 
     | 
    
         
            +
             
     | 
| 
      
 1228 
     | 
    
         
            +
                wwid.tag_last(options)
         
     | 
| 
       911 
1229 
     | 
    
         
             
              end
         
     | 
| 
       912 
1230 
     | 
    
         
             
            end
         
     | 
| 
       913 
1231 
     | 
    
         | 
| 
         @@ -918,13 +1236,19 @@ long_desc %( 
     | 
|
| 
       918 
1236 
     | 
    
         
             
            )
         
     | 
| 
       919 
1237 
     | 
    
         
             
            arg_name '[SECTION|@TAGS]'
         
     | 
| 
       920 
1238 
     | 
    
         
             
            command :show do |c|
         
     | 
| 
      
 1239 
     | 
    
         
            +
              c.example 'doing show Currently', desc: 'Show entries in the Currently section'
         
     | 
| 
      
 1240 
     | 
    
         
            +
              c.example 'doing show @project1', desc: 'Show entries tagged @project1'
         
     | 
| 
      
 1241 
     | 
    
         
            +
              c.example 'doing show Later @doing', desc: 'Show entries from the Later section tagged @doing'
         
     | 
| 
      
 1242 
     | 
    
         
            +
              c.example 'doing show Ideas --from "mon to fri" --tag doing', desc: 'Show entries tagged @doing from the Ideas section added between monday and friday of the current week.'
         
     | 
| 
      
 1243 
     | 
    
         
            +
              c.example 'doing show --interactive Later @doing', desc: 'Create a menu from entries from the Later section tagged @doing to perform batch actions'
         
     | 
| 
      
 1244 
     | 
    
         
            +
             
     | 
| 
       921 
1245 
     | 
    
         
             
              c.desc 'Tag filter, combine multiple tags with a comma. Added for compatibility with other commands.'
         
     | 
| 
       922 
1246 
     | 
    
         
             
              c.arg_name 'TAG'
         
     | 
| 
       923 
1247 
     | 
    
         
             
              c.flag [:tag]
         
     | 
| 
       924 
1248 
     | 
    
         | 
| 
       925 
1249 
     | 
    
         
             
              c.desc 'Tag boolean (AND,OR,NOT)'
         
     | 
| 
       926 
1250 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       927 
     | 
    
         
            -
              c.flag %i[b bool], must_match:  
     | 
| 
      
 1251 
     | 
    
         
            +
              c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'OR'
         
     | 
| 
       928 
1252 
     | 
    
         | 
| 
       929 
1253 
     | 
    
         
             
              c.desc 'Max count to show'
         
     | 
| 
       930 
1254 
     | 
    
         
             
              c.arg_name 'MAX'
         
     | 
| 
         @@ -942,13 +1266,13 @@ command :show do |c| 
     | 
|
| 
       942 
1266 
     | 
    
         
             
              c.arg_name 'DATE_STRING'
         
     | 
| 
       943 
1267 
     | 
    
         
             
              c.flag [:after]
         
     | 
| 
       944 
1268 
     | 
    
         | 
| 
       945 
     | 
    
         
            -
              c.desc 'Search filter, surround with slashes for regex (/query/)'
         
     | 
| 
      
 1269 
     | 
    
         
            +
              c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
         
     | 
| 
       946 
1270 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
       947 
1271 
     | 
    
         
             
              c.flag [:search]
         
     | 
| 
       948 
1272 
     | 
    
         | 
| 
       949 
1273 
     | 
    
         
             
              c.desc 'Sort order (asc/desc)'
         
     | 
| 
       950 
1274 
     | 
    
         
             
              c.arg_name 'ORDER'
         
     | 
| 
       951 
     | 
    
         
            -
              c.flag %i[s sort], must_match:  
     | 
| 
      
 1275 
     | 
    
         
            +
              c.flag %i[s sort], must_match: REGEX_SORT_ORDER, default_value: 'asc'
         
     | 
| 
       952 
1276 
     | 
    
         | 
| 
       953 
1277 
     | 
    
         
             
              c.desc %(
         
     | 
| 
       954 
1278 
     | 
    
         
             
                Date range to show, or a single day to filter date on.
         
     | 
| 
         @@ -966,21 +1290,26 @@ command :show do |c| 
     | 
|
| 
       966 
1290 
     | 
    
         | 
| 
       967 
1291 
     | 
    
         
             
              c.desc 'Sort tags by (name|time)'
         
     | 
| 
       968 
1292 
     | 
    
         
             
              default = 'time'
         
     | 
| 
       969 
     | 
    
         
            -
              default =  
     | 
| 
      
 1293 
     | 
    
         
            +
              default = settings['tag_sort'] || 'name'
         
     | 
| 
       970 
1294 
     | 
    
         
             
              c.arg_name 'KEY'
         
     | 
| 
       971 
1295 
     | 
    
         
             
              c.flag [:tag_sort], must_match: /^(?:name|time)/i, default_value: default
         
     | 
| 
       972 
1296 
     | 
    
         | 
| 
       973 
1297 
     | 
    
         
             
              c.desc 'Tag sort direction (asc|desc)'
         
     | 
| 
       974 
1298 
     | 
    
         
             
              c.arg_name 'DIRECTION'
         
     | 
| 
       975 
     | 
    
         
            -
              c.flag [:tag_order], must_match:  
     | 
| 
      
 1299 
     | 
    
         
            +
              c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
         
     | 
| 
       976 
1300 
     | 
    
         | 
| 
       977 
1301 
     | 
    
         
             
              c.desc 'Only show items with recorded time intervals'
         
     | 
| 
       978 
1302 
     | 
    
         
             
              c.switch [:only_timed], default_value: false, negatable: false
         
     | 
| 
       979 
1303 
     | 
    
         | 
| 
       980 
     | 
    
         
            -
              c.desc ' 
     | 
| 
      
 1304 
     | 
    
         
            +
              c.desc 'Select from a menu of matching entries to perform additional operations'
         
     | 
| 
      
 1305 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
      
 1306 
     | 
    
         
            +
             
     | 
| 
      
 1307 
     | 
    
         
            +
              c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
         
     | 
| 
       981 
1308 
     | 
    
         
             
              c.arg_name 'FORMAT'
         
     | 
| 
       982 
     | 
    
         
            -
              c.flag %i[o output] 
     | 
| 
      
 1309 
     | 
    
         
            +
              c.flag %i[o output]
         
     | 
| 
       983 
1310 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
      
 1311 
     | 
    
         
            +
                raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
         
     | 
| 
      
 1312 
     | 
    
         
            +
             
     | 
| 
       984 
1313 
     | 
    
         
             
                tag_filter = false
         
     | 
| 
       985 
1314 
     | 
    
         
             
                tags = []
         
     | 
| 
       986 
1315 
     | 
    
         
             
                if args.length.positive?
         
     | 
| 
         @@ -995,7 +1324,7 @@ command :show do |c| 
     | 
|
| 
       995 
1324 
     | 
    
         
             
                    section = 'All'
         
     | 
| 
       996 
1325 
     | 
    
         
             
                  else
         
     | 
| 
       997 
1326 
     | 
    
         
             
                    section = wwid.guess_section(args[0])
         
     | 
| 
       998 
     | 
    
         
            -
                     
     | 
| 
      
 1327 
     | 
    
         
            +
                    raise Doing::Errors::InvalidSection, "No such section: #{args[0]}" unless section
         
     | 
| 
       999 
1328 
     | 
    
         | 
| 
       1000 
1329 
     | 
    
         
             
                    args.shift
         
     | 
| 
       1001 
1330 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -1007,72 +1336,51 @@ command :show do |c| 
     | 
|
| 
       1007 
1336 
     | 
    
         
             
                    end
         
     | 
| 
       1008 
1337 
     | 
    
         
             
                  end
         
     | 
| 
       1009 
1338 
     | 
    
         
             
                else
         
     | 
| 
       1010 
     | 
    
         
            -
                  section =  
     | 
| 
      
 1339 
     | 
    
         
            +
                  section = settings['current_section']
         
     | 
| 
       1011 
1340 
     | 
    
         
             
                end
         
     | 
| 
       1012 
1341 
     | 
    
         | 
| 
       1013 
     | 
    
         
            -
                tags.concat(options[:tag]. 
     | 
| 
       1014 
     | 
    
         
            -
                options[:bool] = case options[:bool]
         
     | 
| 
       1015 
     | 
    
         
            -
                                 when /(and|all)/i
         
     | 
| 
       1016 
     | 
    
         
            -
                                  'AND'
         
     | 
| 
       1017 
     | 
    
         
            -
                                 when /(any|or)/i
         
     | 
| 
       1018 
     | 
    
         
            -
                                  'OR'
         
     | 
| 
       1019 
     | 
    
         
            -
                                 when /(not|none)/i
         
     | 
| 
       1020 
     | 
    
         
            -
                                  'NOT'
         
     | 
| 
       1021 
     | 
    
         
            -
                                 else
         
     | 
| 
       1022 
     | 
    
         
            -
                                  'AND'
         
     | 
| 
       1023 
     | 
    
         
            -
                                 end
         
     | 
| 
      
 1342 
     | 
    
         
            +
                tags.concat(options[:tag].to_tags) if options[:tag]
         
     | 
| 
       1024 
1343 
     | 
    
         | 
| 
       1025 
1344 
     | 
    
         
             
                unless tags.empty?
         
     | 
| 
       1026 
1345 
     | 
    
         
             
                  tag_filter = {
         
     | 
| 
       1027 
1346 
     | 
    
         
             
                    'tags' => tags,
         
     | 
| 
       1028 
     | 
    
         
            -
                    'bool' => options[:bool]
         
     | 
| 
      
 1347 
     | 
    
         
            +
                    'bool' => options[:bool].normalize_bool
         
     | 
| 
       1029 
1348 
     | 
    
         
             
                  }
         
     | 
| 
       1030 
1349 
     | 
    
         
             
                end
         
     | 
| 
       1031 
1350 
     | 
    
         | 
| 
       1032 
1351 
     | 
    
         
             
                if options[:from]
         
     | 
| 
      
 1352 
     | 
    
         
            +
             
     | 
| 
       1033 
1353 
     | 
    
         
             
                  date_string = options[:from]
         
     | 
| 
       1034 
1354 
     | 
    
         
             
                  if date_string =~ / (to|through|thru|(un)?til|-+) /
         
     | 
| 
       1035 
1355 
     | 
    
         
             
                    dates = date_string.split(/ (to|through|thru|(un)?til|-+) /)
         
     | 
| 
       1036 
     | 
    
         
            -
                    start = wwid.chronify(dates[0])
         
     | 
| 
       1037 
     | 
    
         
            -
                    finish = wwid.chronify(dates[2])
         
     | 
| 
      
 1356 
     | 
    
         
            +
                    start = wwid.chronify(dates[0], guess: :begin)
         
     | 
| 
      
 1357 
     | 
    
         
            +
                    finish = wwid.chronify(dates[2], guess: :end)
         
     | 
| 
       1038 
1358 
     | 
    
         
             
                  else
         
     | 
| 
       1039 
     | 
    
         
            -
                    start = wwid.chronify(date_string)
         
     | 
| 
      
 1359 
     | 
    
         
            +
                    start = wwid.chronify(date_string, guess: :begin)
         
     | 
| 
       1040 
1360 
     | 
    
         
             
                    finish = false
         
     | 
| 
       1041 
1361 
     | 
    
         
             
                  end
         
     | 
| 
       1042 
     | 
    
         
            -
                   
     | 
| 
      
 1362 
     | 
    
         
            +
                  raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
         
     | 
| 
       1043 
1363 
     | 
    
         
             
                  dates = [start, finish]
         
     | 
| 
       1044 
1364 
     | 
    
         
             
                end
         
     | 
| 
       1045 
1365 
     | 
    
         | 
| 
       1046 
1366 
     | 
    
         
             
                options[:times] = true if options[:totals]
         
     | 
| 
       1047 
1367 
     | 
    
         | 
| 
       1048 
     | 
    
         
            -
                tags_color =  
     | 
| 
      
 1368 
     | 
    
         
            +
                tags_color = settings.key?('tags_color') ? settings['tags_color'] : nil
         
     | 
| 
       1049 
1369 
     | 
    
         | 
| 
       1050 
     | 
    
         
            -
                 
     | 
| 
       1051 
     | 
    
         
            -
             
     | 
| 
       1052 
     | 
    
         
            -
             
     | 
| 
       1053 
     | 
    
         
            -
             
     | 
| 
       1054 
     | 
    
         
            -
             
     | 
| 
       1055 
     | 
    
         
            -
             
     | 
| 
       1056 
     | 
    
         
            -
                 
     | 
| 
       1057 
     | 
    
         
            -
             
     | 
| 
       1058 
     | 
    
         
            -
             
     | 
| 
       1059 
     | 
    
         
            -
             
     | 
| 
       1060 
     | 
    
         
            -
             
     | 
| 
       1061 
     | 
    
         
            -
             
     | 
| 
       1062 
     | 
    
         
            -
             
     | 
| 
       1063 
     | 
    
         
            -
             
     | 
| 
       1064 
     | 
    
         
            -
                  order: options[:s],
         
     | 
| 
       1065 
     | 
    
         
            -
                  output: options[:output],
         
     | 
| 
       1066 
     | 
    
         
            -
                  search: options[:search],
         
     | 
| 
       1067 
     | 
    
         
            -
                  section: section,
         
     | 
| 
       1068 
     | 
    
         
            -
                  sort_tags: options[:sort_tags],
         
     | 
| 
       1069 
     | 
    
         
            -
                  tag_filter: tag_filter,
         
     | 
| 
       1070 
     | 
    
         
            -
                  tag_order: tag_order,
         
     | 
| 
       1071 
     | 
    
         
            -
                  tags_color: tags_color,
         
     | 
| 
       1072 
     | 
    
         
            -
                  times: options[:t],
         
     | 
| 
       1073 
     | 
    
         
            -
                  totals: options[:totals]
         
     | 
| 
       1074 
     | 
    
         
            -
                }
         
     | 
| 
       1075 
     | 
    
         
            -
                puts wwid.list_section(opts)
         
     | 
| 
      
 1370 
     | 
    
         
            +
                opt = options.dup
         
     | 
| 
      
 1371 
     | 
    
         
            +
             
     | 
| 
      
 1372 
     | 
    
         
            +
                opt[:sort_tags] = options[:tag_sort] =~ /^n/i
         
     | 
| 
      
 1373 
     | 
    
         
            +
                opt[:count] = options[:count].to_i
         
     | 
| 
      
 1374 
     | 
    
         
            +
                opt[:date_filter] = dates
         
     | 
| 
      
 1375 
     | 
    
         
            +
                opt[:highlight] = true
         
     | 
| 
      
 1376 
     | 
    
         
            +
                opt[:order] = options[:sort].normalize_order
         
     | 
| 
      
 1377 
     | 
    
         
            +
                opt[:section] = section
         
     | 
| 
      
 1378 
     | 
    
         
            +
                opt[:tag] = nil
         
     | 
| 
      
 1379 
     | 
    
         
            +
                opt[:tag_filter] = tag_filter
         
     | 
| 
      
 1380 
     | 
    
         
            +
                opt[:tag_order] = options[:tag_order].normalize_order
         
     | 
| 
      
 1381 
     | 
    
         
            +
                opt[:tags_color] = tags_color
         
     | 
| 
      
 1382 
     | 
    
         
            +
             
     | 
| 
      
 1383 
     | 
    
         
            +
                Doing::Pager.page wwid.list_section(opt)
         
     | 
| 
       1076 
1384 
     | 
    
         
             
              end
         
     | 
| 
       1077 
1385 
     | 
    
         
             
            end
         
     | 
| 
       1078 
1386 
     | 
    
         | 
| 
         @@ -1084,7 +1392,12 @@ long_desc <<~'EODESC' 
     | 
|
| 
       1084 
1392 
     | 
    
         
             
            EODESC
         
     | 
| 
       1085 
1393 
     | 
    
         | 
| 
       1086 
1394 
     | 
    
         
             
            arg_name 'SEARCH_PATTERN'
         
     | 
| 
       1087 
     | 
    
         
            -
            command [ 
     | 
| 
      
 1395 
     | 
    
         
            +
            command %i[grep search] do |c|
         
     | 
| 
      
 1396 
     | 
    
         
            +
              c.example 'doing grep "doing wiki"', desc: 'Find entries containing "doing wiki" using fuzzy matching'
         
     | 
| 
      
 1397 
     | 
    
         
            +
              c.example 'doing search "\'search command"', desc: 'Find entries containing "search command" using exact matching (search is an alias for grep)'
         
     | 
| 
      
 1398 
     | 
    
         
            +
              c.example 'doing grep "/do.*?wiki.*?@done/"', desc: 'Find entries matching regular expression'
         
     | 
| 
      
 1399 
     | 
    
         
            +
              c.example 'doing search --before 12/21 "doing wiki"', desc: 'Find entries containing "doing wiki" with entry dates before 12/21 of the current year'
         
     | 
| 
      
 1400 
     | 
    
         
            +
             
     | 
| 
       1088 
1401 
     | 
    
         
             
              c.desc 'Section'
         
     | 
| 
       1089 
1402 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       1090 
1403 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
         @@ -1097,9 +1410,9 @@ command [:grep, :search] do |c| 
     | 
|
| 
       1097 
1410 
     | 
    
         
             
              c.arg_name 'DATE_STRING'
         
     | 
| 
       1098 
1411 
     | 
    
         
             
              c.flag [:after]
         
     | 
| 
       1099 
1412 
     | 
    
         | 
| 
       1100 
     | 
    
         
            -
              c.desc  
     | 
| 
      
 1413 
     | 
    
         
            +
              c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
         
     | 
| 
       1101 
1414 
     | 
    
         
             
              c.arg_name 'FORMAT'
         
     | 
| 
       1102 
     | 
    
         
            -
              c.flag %i[o output] 
     | 
| 
      
 1415 
     | 
    
         
            +
              c.flag %i[o output]
         
     | 
| 
       1103 
1416 
     | 
    
         | 
| 
       1104 
1417 
     | 
    
         
             
              c.desc 'Show time intervals on @done tasks'
         
     | 
| 
       1105 
1418 
     | 
    
         
             
              c.switch %i[t times], default_value: true, negatable: true
         
     | 
| 
         @@ -1109,36 +1422,31 @@ command [:grep, :search] do |c| 
     | 
|
| 
       1109 
1422 
     | 
    
         | 
| 
       1110 
1423 
     | 
    
         
             
              c.desc 'Sort tags by (name|time)'
         
     | 
| 
       1111 
1424 
     | 
    
         
             
              default = 'time'
         
     | 
| 
       1112 
     | 
    
         
            -
              default =  
     | 
| 
      
 1425 
     | 
    
         
            +
              default = settings['tag_sort'] || 'name'
         
     | 
| 
       1113 
1426 
     | 
    
         
             
              c.arg_name 'KEY'
         
     | 
| 
       1114 
1427 
     | 
    
         
             
              c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
         
     | 
| 
       1115 
1428 
     | 
    
         | 
| 
       1116 
1429 
     | 
    
         
             
              c.desc 'Only show items with recorded time intervals'
         
     | 
| 
       1117 
1430 
     | 
    
         
             
              c.switch [:only_timed], default_value: false, negatable: false
         
     | 
| 
       1118 
1431 
     | 
    
         | 
| 
      
 1432 
     | 
    
         
            +
              c.desc 'Display an interactive menu of results to perform further operations'
         
     | 
| 
      
 1433 
     | 
    
         
            +
              c.switch %i[i interactive], default_value: false, negatable: false
         
     | 
| 
      
 1434 
     | 
    
         
            +
             
     | 
| 
       1119 
1435 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       1120 
     | 
    
         
            -
                 
     | 
| 
      
 1436 
     | 
    
         
            +
                raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
         
     | 
| 
      
 1437 
     | 
    
         
            +
             
     | 
| 
      
 1438 
     | 
    
         
            +
                tags_color = settings.key?('tags_color') ? settings['tags_color'] : nil
         
     | 
| 
       1121 
1439 
     | 
    
         | 
| 
       1122 
     | 
    
         
            -
                section = wwid.guess_section(options[: 
     | 
| 
      
 1440 
     | 
    
         
            +
                section = wwid.guess_section(options[:section]) if options[:section]
         
     | 
| 
       1123 
1441 
     | 
    
         | 
| 
       1124 
1442 
     | 
    
         
             
                options[:times] = true if options[:totals]
         
     | 
| 
       1125 
1443 
     | 
    
         
             
                options[:sort_tags] = options[:tag_sort] =~ /^n/i
         
     | 
| 
      
 1444 
     | 
    
         
            +
                options[:highlight] = true
         
     | 
| 
      
 1445 
     | 
    
         
            +
                options[:search] = args.join(' ')
         
     | 
| 
      
 1446 
     | 
    
         
            +
                options[:section] = section
         
     | 
| 
      
 1447 
     | 
    
         
            +
                options[:tags_color] = tags_color
         
     | 
| 
       1126 
1448 
     | 
    
         | 
| 
       1127 
     | 
    
         
            -
                 
     | 
| 
       1128 
     | 
    
         
            -
                  after: options[:after],
         
     | 
| 
       1129 
     | 
    
         
            -
                  before: options[:before],
         
     | 
| 
       1130 
     | 
    
         
            -
                  highlight: true,
         
     | 
| 
       1131 
     | 
    
         
            -
                  only_timed: options[:only_timed],
         
     | 
| 
       1132 
     | 
    
         
            -
                  output: options[:output],
         
     | 
| 
       1133 
     | 
    
         
            -
                  search: args.join(' '),
         
     | 
| 
       1134 
     | 
    
         
            -
                  section: section,
         
     | 
| 
       1135 
     | 
    
         
            -
                  sort_tags: options[:sort_tags],
         
     | 
| 
       1136 
     | 
    
         
            -
                  tags_color: tags_color,
         
     | 
| 
       1137 
     | 
    
         
            -
                  times: options[:times],
         
     | 
| 
       1138 
     | 
    
         
            -
                  totals: options[:totals]
         
     | 
| 
       1139 
     | 
    
         
            -
                }
         
     | 
| 
       1140 
     | 
    
         
            -
             
     | 
| 
       1141 
     | 
    
         
            -
                puts wwid.list_section(opts)
         
     | 
| 
      
 1449 
     | 
    
         
            +
                Doing::Pager.page wwid.list_section(options)
         
     | 
| 
       1142 
1450 
     | 
    
         
             
              end
         
     | 
| 
       1143 
1451 
     | 
    
         
             
            end
         
     | 
| 
       1144 
1452 
     | 
    
         | 
| 
         @@ -1146,6 +1454,11 @@ desc 'List recent entries' 
     | 
|
| 
       1146 
1454 
     | 
    
         
             
            default_value 10
         
     | 
| 
       1147 
1455 
     | 
    
         
             
            arg_name 'COUNT'
         
     | 
| 
       1148 
1456 
     | 
    
         
             
            command :recent do |c|
         
     | 
| 
      
 1457 
     | 
    
         
            +
              c.example 'doing recent', desc: 'Show the 10 most recent entries across all sections'
         
     | 
| 
      
 1458 
     | 
    
         
            +
              c.example 'doing recent 20', desc: 'Show the 20 most recent entries across all sections'
         
     | 
| 
      
 1459 
     | 
    
         
            +
              c.example 'doing recent --section Currently 20', desc: 'List the 20 most recent entries from the Currently section'
         
     | 
| 
      
 1460 
     | 
    
         
            +
              c.example 'doing recent --interactive 20', desc: 'Create a menu from the 20 most recent entries to perform batch actions on'
         
     | 
| 
      
 1461 
     | 
    
         
            +
             
     | 
| 
       1149 
1462 
     | 
    
         
             
              c.desc 'Section'
         
     | 
| 
       1150 
1463 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       1151 
1464 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
         @@ -1158,32 +1471,42 @@ command :recent do |c| 
     | 
|
| 
       1158 
1471 
     | 
    
         | 
| 
       1159 
1472 
     | 
    
         
             
              c.desc 'Sort tags by (name|time)'
         
     | 
| 
       1160 
1473 
     | 
    
         
             
              default = 'time'
         
     | 
| 
       1161 
     | 
    
         
            -
              default =  
     | 
| 
      
 1474 
     | 
    
         
            +
              default = settings['tag_sort'] || 'name'
         
     | 
| 
       1162 
1475 
     | 
    
         
             
              c.arg_name 'KEY'
         
     | 
| 
       1163 
1476 
     | 
    
         
             
              c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
         
     | 
| 
       1164 
1477 
     | 
    
         | 
| 
      
 1478 
     | 
    
         
            +
              c.desc 'Select from a menu of matching entries to perform additional operations'
         
     | 
| 
      
 1479 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
      
 1480 
     | 
    
         
            +
             
     | 
| 
       1165 
1481 
     | 
    
         
             
              c.action do |global_options, options, args|
         
     | 
| 
       1166 
1482 
     | 
    
         
             
                section = wwid.guess_section(options[:s]) || options[:s].cap_first
         
     | 
| 
       1167 
1483 
     | 
    
         | 
| 
       1168 
1484 
     | 
    
         
             
                unless global_options[:version]
         
     | 
| 
       1169 
     | 
    
         
            -
                  if  
     | 
| 
       1170 
     | 
    
         
            -
                    config_count =  
     | 
| 
      
 1485 
     | 
    
         
            +
                  if settings['templates']['recent'].key?('count')
         
     | 
| 
      
 1486 
     | 
    
         
            +
                    config_count = settings['templates']['recent']['count'].to_i
         
     | 
| 
       1171 
1487 
     | 
    
         
             
                  else
         
     | 
| 
       1172 
1488 
     | 
    
         
             
                    config_count = 10
         
     | 
| 
       1173 
1489 
     | 
    
         
             
                  end
         
     | 
| 
       1174 
     | 
    
         
            -
             
     | 
| 
      
 1490 
     | 
    
         
            +
             
     | 
| 
      
 1491 
     | 
    
         
            +
                  if options[:interactive]
         
     | 
| 
      
 1492 
     | 
    
         
            +
                    count = 0
         
     | 
| 
      
 1493 
     | 
    
         
            +
                  else
         
     | 
| 
      
 1494 
     | 
    
         
            +
                    count = args.empty? ? config_count : args[0].to_i
         
     | 
| 
      
 1495 
     | 
    
         
            +
                  end
         
     | 
| 
      
 1496 
     | 
    
         
            +
             
     | 
| 
       1175 
1497 
     | 
    
         
             
                  options[:t] = true if options[:totals]
         
     | 
| 
       1176 
1498 
     | 
    
         
             
                  options[:sort_tags] = options[:tag_sort] =~ /^n/i
         
     | 
| 
       1177 
     | 
    
         
            -
                  tags_color =  
     | 
| 
      
 1499 
     | 
    
         
            +
                  tags_color = settings.key?('tags_color') ? settings['tags_color'] : nil
         
     | 
| 
       1178 
1500 
     | 
    
         | 
| 
       1179 
1501 
     | 
    
         
             
                  opts = {
         
     | 
| 
       1180 
1502 
     | 
    
         
             
                    sort_tags: options[:sort_tags],
         
     | 
| 
       1181 
1503 
     | 
    
         
             
                    tags_color: tags_color,
         
     | 
| 
       1182 
1504 
     | 
    
         
             
                    times: options[:t],
         
     | 
| 
       1183 
     | 
    
         
            -
                    totals: options[:totals]
         
     | 
| 
      
 1505 
     | 
    
         
            +
                    totals: options[:totals],
         
     | 
| 
      
 1506 
     | 
    
         
            +
                    interactive: options[:interactive]
         
     | 
| 
       1184 
1507 
     | 
    
         
             
                  }
         
     | 
| 
       1185 
1508 
     | 
    
         | 
| 
       1186 
     | 
    
         
            -
                   
     | 
| 
      
 1509 
     | 
    
         
            +
                  Doing::Pager::page wwid.recent(count, section.cap_first, opts)
         
     | 
| 
       1187 
1510 
     | 
    
         | 
| 
       1188 
1511 
     | 
    
         
             
                end
         
     | 
| 
       1189 
1512 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1191,6 +1514,11 @@ end 
     | 
|
| 
       1191 
1514 
     | 
    
         | 
| 
       1192 
1515 
     | 
    
         
             
            desc 'List entries from today'
         
     | 
| 
       1193 
1516 
     | 
    
         
             
            command :today do |c|
         
     | 
| 
      
 1517 
     | 
    
         
            +
              c.example 'doing today', desc: 'List all entries with start dates between 12am and 11:59PM for the current day'
         
     | 
| 
      
 1518 
     | 
    
         
            +
              c.example 'doing today --section Later', desc: 'List today\'s entries in the Later section'
         
     | 
| 
      
 1519 
     | 
    
         
            +
              c.example 'doing today --before 3pm --after 12pm', desc: 'List entries with start dates between 12pm and 3pm today'
         
     | 
| 
      
 1520 
     | 
    
         
            +
              c.example 'doing today --output json', desc: 'Output entries from today in JSON format'
         
     | 
| 
      
 1521 
     | 
    
         
            +
             
     | 
| 
       1194 
1522 
     | 
    
         
             
              c.desc 'Specify a section'
         
     | 
| 
       1195 
1523 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       1196 
1524 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
         @@ -1203,13 +1531,13 @@ command :today do |c| 
     | 
|
| 
       1203 
1531 
     | 
    
         | 
| 
       1204 
1532 
     | 
    
         
             
              c.desc 'Sort tags by (name|time)'
         
     | 
| 
       1205 
1533 
     | 
    
         
             
              default = 'time'
         
     | 
| 
       1206 
     | 
    
         
            -
              default =  
     | 
| 
      
 1534 
     | 
    
         
            +
              default = settings['tag_sort'] || 'name'
         
     | 
| 
       1207 
1535 
     | 
    
         
             
              c.arg_name 'KEY'
         
     | 
| 
       1208 
1536 
     | 
    
         
             
              c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
         
     | 
| 
       1209 
1537 
     | 
    
         | 
| 
       1210 
     | 
    
         
            -
              c.desc  
     | 
| 
      
 1538 
     | 
    
         
            +
              c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
         
     | 
| 
       1211 
1539 
     | 
    
         
             
              c.arg_name 'FORMAT'
         
     | 
| 
       1212 
     | 
    
         
            -
              c.flag %i[o output] 
     | 
| 
      
 1540 
     | 
    
         
            +
              c.flag %i[o output]
         
     | 
| 
       1213 
1541 
     | 
    
         | 
| 
       1214 
1542 
     | 
    
         
             
              c.desc 'View entries before specified time (e.g. 8am, 12:30pm, 15:00)'
         
     | 
| 
       1215 
1543 
     | 
    
         
             
              c.arg_name 'TIME_STRING'
         
     | 
| 
         @@ -1220,6 +1548,8 @@ command :today do |c| 
     | 
|
| 
       1220 
1548 
     | 
    
         
             
              c.flag [:after]
         
     | 
| 
       1221 
1549 
     | 
    
         | 
| 
       1222 
1550 
     | 
    
         
             
              c.action do |_global_options, options, _args|
         
     | 
| 
      
 1551 
     | 
    
         
            +
                raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
         
     | 
| 
      
 1552 
     | 
    
         
            +
             
     | 
| 
       1223 
1553 
     | 
    
         
             
                options[:t] = true if options[:totals]
         
     | 
| 
       1224 
1554 
     | 
    
         
             
                options[:sort_tags] = options[:tag_sort] =~ /^n/i
         
     | 
| 
       1225 
1555 
     | 
    
         
             
                opt = {
         
     | 
| 
         @@ -1227,9 +1557,10 @@ command :today do |c| 
     | 
|
| 
       1227 
1557 
     | 
    
         
             
                  before: options[:before],
         
     | 
| 
       1228 
1558 
     | 
    
         
             
                  section: options[:section],
         
     | 
| 
       1229 
1559 
     | 
    
         
             
                  sort_tags: options[:sort_tags],
         
     | 
| 
       1230 
     | 
    
         
            -
                  totals: options[:totals]
         
     | 
| 
      
 1560 
     | 
    
         
            +
                  totals: options[:totals],
         
     | 
| 
      
 1561 
     | 
    
         
            +
                  order: settings.dig('templates', 'today', 'order')
         
     | 
| 
       1231 
1562 
     | 
    
         
             
                }
         
     | 
| 
       1232 
     | 
    
         
            -
                 
     | 
| 
      
 1563 
     | 
    
         
            +
                Doing::Pager.page wwid.today(options[:times], options[:output], opt).chomp
         
     | 
| 
       1233 
1564 
     | 
    
         
             
              end
         
     | 
| 
       1234 
1565 
     | 
    
         
             
            end
         
     | 
| 
       1235 
1566 
     | 
    
         | 
| 
         @@ -1239,6 +1570,10 @@ and "2d" would be interpreted as "two days ago." If you use "to" or "through" be 
     | 
|
| 
       1239 
1570 
     | 
    
         
             
            it will create a range.)
         
     | 
| 
       1240 
1571 
     | 
    
         
             
            arg_name 'DATE_STRING'
         
     | 
| 
       1241 
1572 
     | 
    
         
             
            command :on do |c|
         
     | 
| 
      
 1573 
     | 
    
         
            +
              c.example 'doing on friday', desc: 'List entries between 12am and 11:59PM last Friday'
         
     | 
| 
      
 1574 
     | 
    
         
            +
              c.example 'doing on 12/21/2020', desc: 'List entries from Dec 21, 2020'
         
     | 
| 
      
 1575 
     | 
    
         
            +
              c.example 'doing on "3d to 1d"', desc: 'List entries added between 3 days ago and 1 day ago'
         
     | 
| 
      
 1576 
     | 
    
         
            +
             
     | 
| 
       1242 
1577 
     | 
    
         
             
              c.desc 'Section'
         
     | 
| 
       1243 
1578 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       1244 
1579 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
         @@ -1251,38 +1586,40 @@ command :on do |c| 
     | 
|
| 
       1251 
1586 
     | 
    
         | 
| 
       1252 
1587 
     | 
    
         
             
              c.desc 'Sort tags by (name|time)'
         
     | 
| 
       1253 
1588 
     | 
    
         
             
              default = 'time'
         
     | 
| 
       1254 
     | 
    
         
            -
              default =  
     | 
| 
      
 1589 
     | 
    
         
            +
              default = settings['tag_sort'] || 'name'
         
     | 
| 
       1255 
1590 
     | 
    
         
             
              c.arg_name 'KEY'
         
     | 
| 
       1256 
1591 
     | 
    
         
             
              c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
         
     | 
| 
       1257 
1592 
     | 
    
         | 
| 
       1258 
     | 
    
         
            -
              c.desc  
     | 
| 
      
 1593 
     | 
    
         
            +
              c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
         
     | 
| 
       1259 
1594 
     | 
    
         
             
              c.arg_name 'FORMAT'
         
     | 
| 
       1260 
     | 
    
         
            -
              c.flag %i[o output] 
     | 
| 
      
 1595 
     | 
    
         
            +
              c.flag %i[o output]
         
     | 
| 
       1261 
1596 
     | 
    
         | 
| 
       1262 
1597 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       1263 
     | 
    
         
            -
                 
     | 
| 
      
 1598 
     | 
    
         
            +
                raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
         
     | 
| 
      
 1599 
     | 
    
         
            +
             
     | 
| 
      
 1600 
     | 
    
         
            +
                raise Doing::Errors::MissingArgument, 'Missing date argument' if args.empty?
         
     | 
| 
       1264 
1601 
     | 
    
         | 
| 
       1265 
1602 
     | 
    
         
             
                date_string = args.join(' ')
         
     | 
| 
       1266 
1603 
     | 
    
         | 
| 
       1267 
1604 
     | 
    
         
             
                if date_string =~ / (to|through|thru) /
         
     | 
| 
       1268 
1605 
     | 
    
         
             
                  dates = date_string.split(/ (to|through|thru) /)
         
     | 
| 
       1269 
     | 
    
         
            -
                  start = wwid.chronify(dates[0])
         
     | 
| 
       1270 
     | 
    
         
            -
                  finish = wwid.chronify(dates[2])
         
     | 
| 
      
 1606 
     | 
    
         
            +
                  start = wwid.chronify(dates[0], guess: :begin)
         
     | 
| 
      
 1607 
     | 
    
         
            +
                  finish = wwid.chronify(dates[2], guess: :end)
         
     | 
| 
       1271 
1608 
     | 
    
         
             
                else
         
     | 
| 
       1272 
     | 
    
         
            -
                  start = wwid.chronify(date_string)
         
     | 
| 
      
 1609 
     | 
    
         
            +
                  start = wwid.chronify(date_string, guess: :begin)
         
     | 
| 
       1273 
1610 
     | 
    
         
             
                  finish = false
         
     | 
| 
       1274 
1611 
     | 
    
         
             
                end
         
     | 
| 
       1275 
1612 
     | 
    
         | 
| 
       1276 
     | 
    
         
            -
                 
     | 
| 
      
 1613 
     | 
    
         
            +
                raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
         
     | 
| 
       1277 
1614 
     | 
    
         | 
| 
       1278 
1615 
     | 
    
         
             
                message = "Date interpreted as #{start}"
         
     | 
| 
       1279 
1616 
     | 
    
         
             
                message += " to #{finish}" if finish
         
     | 
| 
       1280 
     | 
    
         
            -
                 
     | 
| 
      
 1617 
     | 
    
         
            +
                Doing.logger.debug(message)
         
     | 
| 
       1281 
1618 
     | 
    
         | 
| 
       1282 
1619 
     | 
    
         
             
                options[:t] = true if options[:totals]
         
     | 
| 
       1283 
1620 
     | 
    
         
             
                options[:sort_tags] = options[:tag_sort] =~ /^n/i
         
     | 
| 
       1284 
1621 
     | 
    
         | 
| 
       1285 
     | 
    
         
            -
                 
     | 
| 
      
 1622 
     | 
    
         
            +
                Doing::Pager.page wwid.list_date([start, finish], options[:s], options[:t], options[:output],
         
     | 
| 
       1286 
1623 
     | 
    
         
             
                                    { totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
         
     | 
| 
       1287 
1624 
     | 
    
         
             
              end
         
     | 
| 
       1288 
1625 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1292,6 +1629,9 @@ long_desc %(Date argument can be natural language and are always interpreted as 
     | 
|
| 
       1292 
1629 
     | 
    
         
             
            and "2d" would be interpreted as "two days ago.")
         
     | 
| 
       1293 
1630 
     | 
    
         
             
            arg_name 'DATE_STRING'
         
     | 
| 
       1294 
1631 
     | 
    
         
             
            command :since do |c|
         
     | 
| 
      
 1632 
     | 
    
         
            +
              c.example 'doing since 7/30', desc: 'List all entries created since 12am on 7/30 of the current year'
         
     | 
| 
      
 1633 
     | 
    
         
            +
              c.example 'doing since "monday 3pm" --output json', desc: 'Show entries since 3pm on Monday of the current week, output in JSON format'
         
     | 
| 
      
 1634 
     | 
    
         
            +
             
     | 
| 
       1295 
1635 
     | 
    
         
             
              c.desc 'Section'
         
     | 
| 
       1296 
1636 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       1297 
1637 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
         @@ -1304,47 +1644,52 @@ command :since do |c| 
     | 
|
| 
       1304 
1644 
     | 
    
         | 
| 
       1305 
1645 
     | 
    
         
             
              c.desc 'Sort tags by (name|time)'
         
     | 
| 
       1306 
1646 
     | 
    
         
             
              default = 'time'
         
     | 
| 
       1307 
     | 
    
         
            -
              default =  
     | 
| 
      
 1647 
     | 
    
         
            +
              default = settings['tag_sort'] || 'name'
         
     | 
| 
       1308 
1648 
     | 
    
         
             
              c.arg_name 'KEY'
         
     | 
| 
       1309 
1649 
     | 
    
         
             
              c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
         
     | 
| 
       1310 
1650 
     | 
    
         | 
| 
       1311 
     | 
    
         
            -
              c.desc  
     | 
| 
      
 1651 
     | 
    
         
            +
              c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
         
     | 
| 
       1312 
1652 
     | 
    
         
             
              c.arg_name 'FORMAT'
         
     | 
| 
       1313 
     | 
    
         
            -
              c.flag %i[o output] 
     | 
| 
      
 1653 
     | 
    
         
            +
              c.flag %i[o output]
         
     | 
| 
       1314 
1654 
     | 
    
         | 
| 
       1315 
1655 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       1316 
     | 
    
         
            -
                 
     | 
| 
      
 1656 
     | 
    
         
            +
                raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
         
     | 
| 
      
 1657 
     | 
    
         
            +
             
     | 
| 
      
 1658 
     | 
    
         
            +
                raise Doing::Errors::MissingArgument, 'Missing date argument' if args.empty?
         
     | 
| 
       1317 
1659 
     | 
    
         | 
| 
       1318 
1660 
     | 
    
         
             
                date_string = args.join(' ')
         
     | 
| 
       1319 
1661 
     | 
    
         | 
| 
       1320 
     | 
    
         
            -
                date_string  
     | 
| 
       1321 
     | 
    
         
            -
                date_string.sub!(/( 
     | 
| 
      
 1662 
     | 
    
         
            +
                date_string.sub!(/(day) (\d)/, '\1 at \2')
         
     | 
| 
      
 1663 
     | 
    
         
            +
                date_string.sub!(/(\d+)d( ago)?/, '\1 days ago')
         
     | 
| 
       1322 
1664 
     | 
    
         | 
| 
       1323 
     | 
    
         
            -
                start = wwid.chronify(date_string)
         
     | 
| 
      
 1665 
     | 
    
         
            +
                start = wwid.chronify(date_string, guess: :begin)
         
     | 
| 
       1324 
1666 
     | 
    
         
             
                finish = Time.now
         
     | 
| 
       1325 
1667 
     | 
    
         | 
| 
       1326 
     | 
    
         
            -
                 
     | 
| 
      
 1668 
     | 
    
         
            +
                raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
         
     | 
| 
       1327 
1669 
     | 
    
         | 
| 
       1328 
     | 
    
         
            -
                 
     | 
| 
       1329 
     | 
    
         
            -
                wwid.results.push(message)
         
     | 
| 
      
 1670 
     | 
    
         
            +
                Doing.logger.debug("Date interpreted as #{start} through the current time")
         
     | 
| 
       1330 
1671 
     | 
    
         | 
| 
       1331 
1672 
     | 
    
         
             
                options[:t] = true if options[:totals]
         
     | 
| 
       1332 
1673 
     | 
    
         
             
                options[:sort_tags] = options[:tag_sort] =~ /^n/i
         
     | 
| 
       1333 
1674 
     | 
    
         | 
| 
       1334 
     | 
    
         
            -
                 
     | 
| 
      
 1675 
     | 
    
         
            +
                Doing::Pager.page wwid.list_date([start, finish], options[:s], options[:t], options[:output],
         
     | 
| 
       1335 
1676 
     | 
    
         
             
                                    { totals: options[:totals], sort_tags: options[:sort_tags] }).chomp
         
     | 
| 
       1336 
1677 
     | 
    
         
             
              end
         
     | 
| 
       1337 
1678 
     | 
    
         
             
            end
         
     | 
| 
       1338 
1679 
     | 
    
         | 
| 
       1339 
1680 
     | 
    
         
             
            desc 'List entries from yesterday'
         
     | 
| 
       1340 
1681 
     | 
    
         
             
            command :yesterday do |c|
         
     | 
| 
      
 1682 
     | 
    
         
            +
              c.example 'doing yesterday', desc: 'List all entries from the previous day'
         
     | 
| 
      
 1683 
     | 
    
         
            +
              c.example 'doing yesterday --after 8am --before 5pm', desc: 'List entries from the previous day between 8am and 5pm'
         
     | 
| 
      
 1684 
     | 
    
         
            +
              c.example 'doing yesterday --totals', desc: 'List entries from previous day, including tag timers'
         
     | 
| 
      
 1685 
     | 
    
         
            +
             
     | 
| 
       1341 
1686 
     | 
    
         
             
              c.desc 'Specify a section'
         
     | 
| 
       1342 
1687 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       1343 
1688 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
       1344 
1689 
     | 
    
         | 
| 
       1345 
     | 
    
         
            -
              c.desc  
     | 
| 
      
 1690 
     | 
    
         
            +
              c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
         
     | 
| 
       1346 
1691 
     | 
    
         
             
              c.arg_name 'FORMAT'
         
     | 
| 
       1347 
     | 
    
         
            -
              c.flag %i[o output] 
     | 
| 
      
 1692 
     | 
    
         
            +
              c.flag %i[o output]
         
     | 
| 
       1348 
1693 
     | 
    
         | 
| 
       1349 
1694 
     | 
    
         
             
              c.desc 'Show time intervals on @done tasks'
         
     | 
| 
       1350 
1695 
     | 
    
         
             
              c.switch %i[t times], default_value: true, negatable: true
         
     | 
| 
         @@ -1354,7 +1699,7 @@ command :yesterday do |c| 
     | 
|
| 
       1354 
1699 
     | 
    
         | 
| 
       1355 
1700 
     | 
    
         
             
              c.desc 'Sort tags by (name|time)'
         
     | 
| 
       1356 
1701 
     | 
    
         
             
              default = 'time'
         
     | 
| 
       1357 
     | 
    
         
            -
              default =  
     | 
| 
      
 1702 
     | 
    
         
            +
              default = settings['tag_sort'] || 'name'
         
     | 
| 
       1358 
1703 
     | 
    
         
             
              c.arg_name 'KEY'
         
     | 
| 
       1359 
1704 
     | 
    
         
             
              c.flag [:tag_sort], must_match: /^(?:name|time)$/i, default_value: default
         
     | 
| 
       1360 
1705 
     | 
    
         | 
| 
         @@ -1368,33 +1713,40 @@ command :yesterday do |c| 
     | 
|
| 
       1368 
1713 
     | 
    
         | 
| 
       1369 
1714 
     | 
    
         
             
              c.desc 'Tag sort direction (asc|desc)'
         
     | 
| 
       1370 
1715 
     | 
    
         
             
              c.arg_name 'DIRECTION'
         
     | 
| 
       1371 
     | 
    
         
            -
              c.flag [:tag_order], must_match:  
     | 
| 
      
 1716 
     | 
    
         
            +
              c.flag [:tag_order], must_match: REGEX_SORT_ORDER, default_value: 'asc'
         
     | 
| 
       1372 
1717 
     | 
    
         | 
| 
       1373 
1718 
     | 
    
         
             
              c.action do |_global_options, options, _args|
         
     | 
| 
       1374 
     | 
    
         
            -
                 
     | 
| 
       1375 
     | 
    
         
            -
             
     | 
| 
       1376 
     | 
    
         
            -
                            else
         
     | 
| 
       1377 
     | 
    
         
            -
                              'asc'
         
     | 
| 
       1378 
     | 
    
         
            -
                            end
         
     | 
| 
      
 1719 
     | 
    
         
            +
                raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
         
     | 
| 
      
 1720 
     | 
    
         
            +
             
     | 
| 
       1379 
1721 
     | 
    
         
             
                options[:sort_tags] = options[:tag_sort] =~ /^n/i
         
     | 
| 
      
 1722 
     | 
    
         
            +
             
     | 
| 
       1380 
1723 
     | 
    
         
             
                opt = {
         
     | 
| 
       1381 
1724 
     | 
    
         
             
                  after: options[:after],
         
     | 
| 
       1382 
1725 
     | 
    
         
             
                  before: options[:before],
         
     | 
| 
       1383 
1726 
     | 
    
         
             
                  sort_tags: options[:sort_tags],
         
     | 
| 
       1384 
     | 
    
         
            -
                  tag_order: options[:tag_order],
         
     | 
| 
       1385 
     | 
    
         
            -
                  totals: options[:totals]
         
     | 
| 
      
 1727 
     | 
    
         
            +
                  tag_order: options[:tag_order].normalize_order,
         
     | 
| 
      
 1728 
     | 
    
         
            +
                  totals: options[:totals],
         
     | 
| 
      
 1729 
     | 
    
         
            +
                  order: settings.dig('templates', 'today', 'order')
         
     | 
| 
       1386 
1730 
     | 
    
         
             
                }
         
     | 
| 
       1387 
     | 
    
         
            -
                 
     | 
| 
      
 1731 
     | 
    
         
            +
                Doing::Pager.page wwid.yesterday(options[:section], options[:times], options[:output], opt).chomp
         
     | 
| 
       1388 
1732 
     | 
    
         
             
              end
         
     | 
| 
       1389 
1733 
     | 
    
         
             
            end
         
     | 
| 
       1390 
1734 
     | 
    
         | 
| 
       1391 
1735 
     | 
    
         
             
            desc 'Show the last entry, optionally edit'
         
     | 
| 
       1392 
1736 
     | 
    
         
             
            command :last do |c|
         
     | 
| 
      
 1737 
     | 
    
         
            +
              c.example 'doing last', desc: 'Show the most recent entry in all sections'
         
     | 
| 
      
 1738 
     | 
    
         
            +
              c.example 'doing last -s Later', desc: 'Show the most recent entry in the Later section'
         
     | 
| 
      
 1739 
     | 
    
         
            +
              c.example 'doing last --tag project1,work --bool AND', desc: 'Show most recent entry tagged @project1 and @work'
         
     | 
| 
      
 1740 
     | 
    
         
            +
              c.example 'doing last --search "side hustle"', desc: 'Show most recent entry containing "side hustle" (fuzzy matching)'
         
     | 
| 
      
 1741 
     | 
    
         
            +
              c.example 'doing last --search "\'side hustle"', desc: 'Show most recent entry containing "side hustle" (exact match)'
         
     | 
| 
      
 1742 
     | 
    
         
            +
              c.example 'doing last --edit', desc: 'Open the most recent entry in an editor for modifications'
         
     | 
| 
      
 1743 
     | 
    
         
            +
              c.example 'doing last --search "\'side hustle" --edit', desc: 'Open most recent entry containing "side hustle" (exact match) in editor'
         
     | 
| 
      
 1744 
     | 
    
         
            +
             
     | 
| 
       1393 
1745 
     | 
    
         
             
              c.desc 'Specify a section'
         
     | 
| 
       1394 
1746 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       1395 
1747 
     | 
    
         
             
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
       1396 
1748 
     | 
    
         | 
| 
       1397 
     | 
    
         
            -
              c.desc "Edit entry with #{ 
     | 
| 
      
 1749 
     | 
    
         
            +
              c.desc "Edit entry with #{Doing::Util.default_editor}"
         
     | 
| 
       1398 
1750 
     | 
    
         
             
              c.switch %i[e editor], negatable: false, default_value: false
         
     | 
| 
       1399 
1751 
     | 
    
         | 
| 
       1400 
1752 
     | 
    
         
             
              c.desc 'Tag filter, combine multiple tags with a comma.'
         
     | 
| 
         @@ -1403,19 +1755,19 @@ command :last do |c| 
     | 
|
| 
       1403 
1755 
     | 
    
         | 
| 
       1404 
1756 
     | 
    
         
             
              c.desc 'Tag boolean'
         
     | 
| 
       1405 
1757 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       1406 
     | 
    
         
            -
              c.flag [:bool], must_match:  
     | 
| 
      
 1758 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
       1407 
1759 
     | 
    
         | 
| 
       1408 
     | 
    
         
            -
              c.desc 'Search filter, surround with slashes for regex (/query/)'
         
     | 
| 
      
 1760 
     | 
    
         
            +
              c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
         
     | 
| 
       1409 
1761 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
       1410 
1762 
     | 
    
         
             
              c.flag [:search]
         
     | 
| 
       1411 
1763 
     | 
    
         | 
| 
       1412 
     | 
    
         
            -
              c.action do | 
     | 
| 
       1413 
     | 
    
         
            -
                 
     | 
| 
      
 1764 
     | 
    
         
            +
              c.action do |global_options, options, _args|
         
     | 
| 
      
 1765 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
         
     | 
| 
       1414 
1766 
     | 
    
         | 
| 
       1415 
1767 
     | 
    
         
             
                if options[:tag].nil?
         
     | 
| 
       1416 
1768 
     | 
    
         
             
                  tags = []
         
     | 
| 
       1417 
1769 
     | 
    
         
             
                else
         
     | 
| 
       1418 
     | 
    
         
            -
                  tags = options[:tag]. 
     | 
| 
      
 1770 
     | 
    
         
            +
                  tags = options[:tag].to_tags
         
     | 
| 
       1419 
1771 
     | 
    
         
             
                  options[:bool] = case options[:bool]
         
     | 
| 
       1420 
1772 
     | 
    
         
             
                                   when /(any|or)/i
         
     | 
| 
       1421 
1773 
     | 
    
         
             
                                    :or
         
     | 
| 
         @@ -1430,7 +1782,7 @@ command :last do |c| 
     | 
|
| 
       1430 
1782 
     | 
    
         
             
                if options[:editor]
         
     | 
| 
       1431 
1783 
     | 
    
         
             
                  wwid.edit_last(section: options[:s], options: { search: options[:search], tag: tags, tag_bool: options[:bool] })
         
     | 
| 
       1432 
1784 
     | 
    
         
             
                else
         
     | 
| 
       1433 
     | 
    
         
            -
                   
     | 
| 
      
 1785 
     | 
    
         
            +
                  Doing::Pager::page wwid.last(times: true, section: options[:s],
         
     | 
| 
       1434 
1786 
     | 
    
         
             
                                 options: { search: options[:search], tag: tags, tag_bool: options[:bool] }).strip
         
     | 
| 
       1435 
1787 
     | 
    
         
             
                end
         
     | 
| 
       1436 
1788 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1452,17 +1804,19 @@ command :choose do |c| 
     | 
|
| 
       1452 
1804 
     | 
    
         
             
              c.action do |_global_options, _options, _args|
         
     | 
| 
       1453 
1805 
     | 
    
         
             
                section = wwid.choose_section
         
     | 
| 
       1454 
1806 
     | 
    
         | 
| 
       1455 
     | 
    
         
            -
                 
     | 
| 
      
 1807 
     | 
    
         
            +
                Doing::Pager.page wwid.list_section({ section: section.cap_first, count: 0 }) if section
         
     | 
| 
       1456 
1808 
     | 
    
         
             
              end
         
     | 
| 
       1457 
1809 
     | 
    
         
             
            end
         
     | 
| 
       1458 
1810 
     | 
    
         | 
| 
       1459 
1811 
     | 
    
         
             
            desc 'Add a new section to the "doing" file'
         
     | 
| 
       1460 
1812 
     | 
    
         
             
            arg_name 'SECTION_NAME'
         
     | 
| 
       1461 
1813 
     | 
    
         
             
            command :add_section do |c|
         
     | 
| 
      
 1814 
     | 
    
         
            +
              c.example 'doing add_section Ideas', desc: 'Add a section called Ideas to the doing file'
         
     | 
| 
      
 1815 
     | 
    
         
            +
             
     | 
| 
       1462 
1816 
     | 
    
         
             
              c.action do |_global_options, _options, args|
         
     | 
| 
       1463 
     | 
    
         
            -
                 
     | 
| 
      
 1817 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, "Section #{args[0]} already exists" if wwid.sections.include?(args[0])
         
     | 
| 
       1464 
1818 
     | 
    
         | 
| 
       1465 
     | 
    
         
            -
                wwid.add_section(args 
     | 
| 
      
 1819 
     | 
    
         
            +
                wwid.add_section(args.join(' ').cap_first)
         
     | 
| 
       1466 
1820 
     | 
    
         
             
                wwid.write(wwid.doing_file)
         
     | 
| 
       1467 
1821 
     | 
    
         
             
              end
         
     | 
| 
       1468 
1822 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1470,18 +1824,42 @@ end 
     | 
|
| 
       1470 
1824 
     | 
    
         
             
            desc 'List available color variables for configuration templates and views'
         
     | 
| 
       1471 
1825 
     | 
    
         
             
            command :colors do |c|
         
     | 
| 
       1472 
1826 
     | 
    
         
             
              c.action do |_global_options, _options, _args|
         
     | 
| 
       1473 
     | 
    
         
            -
                clrs = wwid.colors
         
     | 
| 
       1474 
1827 
     | 
    
         
             
                bgs = []
         
     | 
| 
       1475 
1828 
     | 
    
         
             
                fgs = []
         
     | 
| 
       1476 
     | 
    
         
            -
                 
     | 
| 
       1477 
     | 
    
         
            -
                  if  
     | 
| 
       1478 
     | 
    
         
            -
                    bgs.push("#{ 
     | 
| 
      
 1829 
     | 
    
         
            +
                colors::attributes.each do |color|
         
     | 
| 
      
 1830 
     | 
    
         
            +
                  if color.to_s =~ /bg/
         
     | 
| 
      
 1831 
     | 
    
         
            +
                    bgs.push("#{colors.send(color, "    ")}#{colors.default} <-- #{color.to_s}")
         
     | 
| 
       1479 
1832 
     | 
    
         
             
                  else
         
     | 
| 
       1480 
     | 
    
         
            -
                    fgs.push("#{ 
     | 
| 
      
 1833 
     | 
    
         
            +
                    fgs.push("#{colors.send(color, "XXXX")}#{colors.default} <-- #{color.to_s}")
         
     | 
| 
       1481 
1834 
     | 
    
         
             
                  end
         
     | 
| 
       1482 
1835 
     | 
    
         
             
                end
         
     | 
| 
       1483 
     | 
    
         
            -
                 
     | 
| 
       1484 
     | 
    
         
            -
                 
     | 
| 
      
 1836 
     | 
    
         
            +
                out = []
         
     | 
| 
      
 1837 
     | 
    
         
            +
                out << fgs.join("\n")
         
     | 
| 
      
 1838 
     | 
    
         
            +
                out << bgs.join("\n")
         
     | 
| 
      
 1839 
     | 
    
         
            +
                Doing::Pager.page out.join("\n")
         
     | 
| 
      
 1840 
     | 
    
         
            +
              end
         
     | 
| 
      
 1841 
     | 
    
         
            +
            end
         
     | 
| 
      
 1842 
     | 
    
         
            +
             
     | 
| 
      
 1843 
     | 
    
         
            +
            desc 'List installed plugins'
         
     | 
| 
      
 1844 
     | 
    
         
            +
            long_desc %(Lists available plugins, including user-installed plugins.
         
     | 
| 
      
 1845 
     | 
    
         
            +
             
     | 
| 
      
 1846 
     | 
    
         
            +
            Export plugins are available with the `--output` flag on commands that support it.
         
     | 
| 
      
 1847 
     | 
    
         
            +
             
     | 
| 
      
 1848 
     | 
    
         
            +
            Import plugins are available using `doing import --type PLUGIN`.
         
     | 
| 
      
 1849 
     | 
    
         
            +
            )
         
     | 
| 
      
 1850 
     | 
    
         
            +
            command :plugins do |c|
         
     | 
| 
      
 1851 
     | 
    
         
            +
              c.example 'doing plugins', desc: 'List all plugins'
         
     | 
| 
      
 1852 
     | 
    
         
            +
              c.example 'doing plugins -t import', desc: 'List all import plugins'
         
     | 
| 
      
 1853 
     | 
    
         
            +
             
     | 
| 
      
 1854 
     | 
    
         
            +
              c.desc 'List plugins of type (import, export)'
         
     | 
| 
      
 1855 
     | 
    
         
            +
              c.arg_name 'TYPE'
         
     | 
| 
      
 1856 
     | 
    
         
            +
              c.flag %i[t type], must_match: /^[iea].*$/i, default_value: 'all'
         
     | 
| 
      
 1857 
     | 
    
         
            +
             
     | 
| 
      
 1858 
     | 
    
         
            +
              c.desc 'List in single column for completion'
         
     | 
| 
      
 1859 
     | 
    
         
            +
              c.switch %i[c column], default_value: false
         
     | 
| 
      
 1860 
     | 
    
         
            +
             
     | 
| 
      
 1861 
     | 
    
         
            +
              c.action do |_global_options, options, _args|
         
     | 
| 
      
 1862 
     | 
    
         
            +
                Doing::Plugins.list_plugins(options)
         
     | 
| 
       1485 
1863 
     | 
    
         
             
              end
         
     | 
| 
       1486 
1864 
     | 
    
         
             
            end
         
     | 
| 
       1487 
1865 
     | 
    
         | 
| 
         @@ -1489,6 +1867,9 @@ desc 'Display a user-created view' 
     | 
|
| 
       1489 
1867 
     | 
    
         
             
            long_desc 'Command line options override view configuration'
         
     | 
| 
       1490 
1868 
     | 
    
         
             
            arg_name 'VIEW_NAME'
         
     | 
| 
       1491 
1869 
     | 
    
         
             
            command :view do |c|
         
     | 
| 
      
 1870 
     | 
    
         
            +
              c.example 'doing view color', desc: 'Display entries according to config for view "color"'
         
     | 
| 
      
 1871 
     | 
    
         
            +
              c.example 'doing view color --section Archive --count 10', desc: 'Display view "color", overriding some configured settings'
         
     | 
| 
      
 1872 
     | 
    
         
            +
             
     | 
| 
       1492 
1873 
     | 
    
         
             
              c.desc 'Section'
         
     | 
| 
       1493 
1874 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
       1494 
1875 
     | 
    
         
             
              c.flag %i[s section]
         
     | 
| 
         @@ -1497,9 +1878,9 @@ command :view do |c| 
     | 
|
| 
       1497 
1878 
     | 
    
         
             
              c.arg_name 'COUNT'
         
     | 
| 
       1498 
1879 
     | 
    
         
             
              c.flag %i[c count], must_match: /^\d+$/, type: Integer
         
     | 
| 
       1499 
1880 
     | 
    
         | 
| 
       1500 
     | 
    
         
            -
              c.desc  
     | 
| 
      
 1881 
     | 
    
         
            +
              c.desc "Output to export format (#{Doing::Plugins.plugin_names(type: :export)})"
         
     | 
| 
       1501 
1882 
     | 
    
         
             
              c.arg_name 'FORMAT'
         
     | 
| 
       1502 
     | 
    
         
            -
              c.flag %i[o output] 
     | 
| 
      
 1883 
     | 
    
         
            +
              c.flag %i[o output]
         
     | 
| 
       1503 
1884 
     | 
    
         | 
| 
       1504 
1885 
     | 
    
         
             
              c.desc 'Show time intervals on @done tasks'
         
     | 
| 
       1505 
1886 
     | 
    
         
             
              c.switch %i[t times], default_value: true, negatable: true
         
     | 
| 
         @@ -1516,9 +1897,9 @@ command :view do |c| 
     | 
|
| 
       1516 
1897 
     | 
    
         | 
| 
       1517 
1898 
     | 
    
         
             
              c.desc 'Tag boolean (AND,OR,NOT)'
         
     | 
| 
       1518 
1899 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       1519 
     | 
    
         
            -
              c.flag %i[b bool], must_match:  
     | 
| 
      
 1900 
     | 
    
         
            +
              c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'OR'
         
     | 
| 
       1520 
1901 
     | 
    
         | 
| 
       1521 
     | 
    
         
            -
              c.desc 'Search filter, surround with slashes for regex (/query/)'
         
     | 
| 
      
 1902 
     | 
    
         
            +
              c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
         
     | 
| 
       1522 
1903 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
       1523 
1904 
     | 
    
         
             
              c.flag [:search]
         
     | 
| 
       1524 
1905 
     | 
    
         | 
| 
         @@ -1528,7 +1909,7 @@ command :view do |c| 
     | 
|
| 
       1528 
1909 
     | 
    
         | 
| 
       1529 
1910 
     | 
    
         
             
              c.desc 'Tag sort direction (asc|desc)'
         
     | 
| 
       1530 
1911 
     | 
    
         
             
              c.arg_name 'DIRECTION'
         
     | 
| 
       1531 
     | 
    
         
            -
              c.flag [:tag_order], must_match:  
     | 
| 
      
 1912 
     | 
    
         
            +
              c.flag [:tag_order], must_match: REGEX_SORT_ORDER
         
     | 
| 
       1532 
1913 
     | 
    
         | 
| 
       1533 
1914 
     | 
    
         
             
              c.desc 'View entries older than date'
         
     | 
| 
       1534 
1915 
     | 
    
         
             
              c.arg_name 'DATE_STRING'
         
     | 
| 
         @@ -1541,8 +1922,13 @@ command :view do |c| 
     | 
|
| 
       1541 
1922 
     | 
    
         
             
              c.desc 'Only show items with recorded time intervals (override view settings)'
         
     | 
| 
       1542 
1923 
     | 
    
         
             
              c.switch [:only_timed], default_value: false, negatable: false
         
     | 
| 
       1543 
1924 
     | 
    
         | 
| 
      
 1925 
     | 
    
         
            +
              c.desc 'Select from a menu of matching entries to perform additional operations'
         
     | 
| 
      
 1926 
     | 
    
         
            +
              c.switch %i[i interactive]
         
     | 
| 
      
 1927 
     | 
    
         
            +
             
     | 
| 
       1544 
1928 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       1545 
     | 
    
         
            -
                 
     | 
| 
      
 1929 
     | 
    
         
            +
                raise InvalidExportType, "Invalid export type: #{options[:output]}" if options[:output] && options[:output] !~ Doing::Plugins.plugin_regex(type: :export)
         
     | 
| 
      
 1930 
     | 
    
         
            +
             
     | 
| 
      
 1931 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, '--tag and --search can not be used together' if options[:tag] && options[:search]
         
     | 
| 
       1546 
1932 
     | 
    
         | 
| 
       1547 
1933 
     | 
    
         
             
                title = if args.empty?
         
     | 
| 
       1548 
1934 
     | 
    
         
             
                          wwid.choose_view
         
     | 
| 
         @@ -1553,11 +1939,12 @@ command :view do |c| 
     | 
|
| 
       1553 
1939 
     | 
    
         
             
                if options[:section]
         
     | 
| 
       1554 
1940 
     | 
    
         
             
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
       1555 
1941 
     | 
    
         
             
                else
         
     | 
| 
       1556 
     | 
    
         
            -
                  section =  
     | 
| 
      
 1942 
     | 
    
         
            +
                  section = settings['current_section']
         
     | 
| 
       1557 
1943 
     | 
    
         
             
                end
         
     | 
| 
       1558 
1944 
     | 
    
         | 
| 
       1559 
1945 
     | 
    
         
             
                view = wwid.get_view(title)
         
     | 
| 
       1560 
1946 
     | 
    
         
             
                if view
         
     | 
| 
      
 1947 
     | 
    
         
            +
                  page_title = view.key?('title') ? view['title'] : title.cap_first
         
     | 
| 
       1561 
1948 
     | 
    
         
             
                  only_timed = if (view.key?('only_timed') && view['only_timed']) || options[:only_timed]
         
     | 
| 
       1562 
1949 
     | 
    
         
             
                                 true
         
     | 
| 
       1563 
1950 
     | 
    
         
             
                               else
         
     | 
| 
         @@ -1565,7 +1952,7 @@ command :view do |c| 
     | 
|
| 
       1565 
1952 
     | 
    
         
             
                               end
         
     | 
| 
       1566 
1953 
     | 
    
         | 
| 
       1567 
1954 
     | 
    
         
             
                  template = view.key?('template') ? view['template'] : nil
         
     | 
| 
       1568 
     | 
    
         
            -
                   
     | 
| 
      
 1955 
     | 
    
         
            +
                  date_format = view.key?('date_format') ? view['date_format'] : nil
         
     | 
| 
       1569 
1956 
     | 
    
         
             
                  tags_color = view.key?('tags_color') ? view['tags_color'] : nil
         
     | 
| 
       1570 
1957 
     | 
    
         
             
                  tag_filter = false
         
     | 
| 
       1571 
1958 
     | 
    
         
             
                  if options[:tag]
         
     | 
| 
         @@ -1583,7 +1970,7 @@ command :view do |c| 
     | 
|
| 
       1583 
1970 
     | 
    
         
             
                  end
         
     | 
| 
       1584 
1971 
     | 
    
         | 
| 
       1585 
1972 
     | 
    
         
             
                  # If the -o/--output flag was specified, override any default in the view template
         
     | 
| 
       1586 
     | 
    
         
            -
                  options[: 
     | 
| 
      
 1973 
     | 
    
         
            +
                  options[:output] ||= view.key?('output_format') ? view['output_format'] : 'template'
         
     | 
| 
       1587 
1974 
     | 
    
         | 
| 
       1588 
1975 
     | 
    
         
             
                  count = if options[:c]
         
     | 
| 
       1589 
1976 
     | 
    
         
             
                            options[:c]
         
     | 
| 
         @@ -1593,18 +1980,23 @@ command :view do |c| 
     | 
|
| 
       1593 
1980 
     | 
    
         
             
                  section = if options[:s]
         
     | 
| 
       1594 
1981 
     | 
    
         
             
                              section
         
     | 
| 
       1595 
1982 
     | 
    
         
             
                            else
         
     | 
| 
       1596 
     | 
    
         
            -
                              view.key?('section') ? view['section'] :  
     | 
| 
      
 1983 
     | 
    
         
            +
                              view.key?('section') ? view['section'] : settings['current_section']
         
     | 
| 
       1597 
1984 
     | 
    
         
             
                            end
         
     | 
| 
       1598 
     | 
    
         
            -
                  order = view.key?('order') ? view['order'] : 'asc'
         
     | 
| 
      
 1985 
     | 
    
         
            +
                  order = view.key?('order') ? view['order'].normalize_order : 'asc'
         
     | 
| 
       1599 
1986 
     | 
    
         | 
| 
       1600 
1987 
     | 
    
         
             
                  totals = if options[:totals]
         
     | 
| 
       1601 
1988 
     | 
    
         
             
                             true
         
     | 
| 
       1602 
1989 
     | 
    
         
             
                           else
         
     | 
| 
       1603 
1990 
     | 
    
         
             
                             view.key?('totals') ? view['totals'] : false
         
     | 
| 
       1604 
1991 
     | 
    
         
             
                           end
         
     | 
| 
      
 1992 
     | 
    
         
            +
                  tag_order = if options[:tag_order]
         
     | 
| 
      
 1993 
     | 
    
         
            +
                                options[:tag_order].normalize_order
         
     | 
| 
      
 1994 
     | 
    
         
            +
                              else
         
     | 
| 
      
 1995 
     | 
    
         
            +
                                view.key?('tag_order') ? view['tag_order'].normalize_order : 'asc'
         
     | 
| 
      
 1996 
     | 
    
         
            +
                              end
         
     | 
| 
       1605 
1997 
     | 
    
         | 
| 
       1606 
1998 
     | 
    
         
             
                  options[:t] = true if totals
         
     | 
| 
       1607 
     | 
    
         
            -
                  options[:output]&.downcase 
     | 
| 
      
 1999 
     | 
    
         
            +
                  output_format = options[:output]&.downcase || 'template'
         
     | 
| 
       1608 
2000 
     | 
    
         | 
| 
       1609 
2001 
     | 
    
         
             
                  options[:sort_tags] = if options[:tag_sort]
         
     | 
| 
       1610 
2002 
     | 
    
         
             
                                          options[:tag_sort] =~ /^n/i ? true : false
         
     | 
| 
         @@ -1613,40 +2005,50 @@ command :view do |c| 
     | 
|
| 
       1613 
2005 
     | 
    
         
             
                                        else
         
     | 
| 
       1614 
2006 
     | 
    
         
             
                                          false
         
     | 
| 
       1615 
2007 
     | 
    
         
             
                                        end
         
     | 
| 
      
 2008 
     | 
    
         
            +
                  if view.key?('after') && !options[:after]
         
     | 
| 
      
 2009 
     | 
    
         
            +
                    options[:after] = view['after']
         
     | 
| 
      
 2010 
     | 
    
         
            +
                  end
         
     | 
| 
       1616 
2011 
     | 
    
         | 
| 
       1617 
     | 
    
         
            -
                   
     | 
| 
       1618 
     | 
    
         
            -
             
     | 
| 
       1619 
     | 
    
         
            -
             
     | 
| 
       1620 
     | 
    
         
            -
                                view['tag_order'] =~ /^d/i ? 'desc' : 'asc'
         
     | 
| 
       1621 
     | 
    
         
            -
                              else
         
     | 
| 
       1622 
     | 
    
         
            -
                                'asc'
         
     | 
| 
       1623 
     | 
    
         
            -
                              end
         
     | 
| 
      
 2012 
     | 
    
         
            +
                  if view.key?('before') && !options[:before]
         
     | 
| 
      
 2013 
     | 
    
         
            +
                    options[:before] = view['before']
         
     | 
| 
      
 2014 
     | 
    
         
            +
                  end
         
     | 
| 
       1624 
2015 
     | 
    
         | 
| 
       1625 
     | 
    
         
            -
                   
     | 
| 
       1626 
     | 
    
         
            -
                     
     | 
| 
       1627 
     | 
    
         
            -
                     
     | 
| 
       1628 
     | 
    
         
            -
             
     | 
| 
       1629 
     | 
    
         
            -
             
     | 
| 
       1630 
     | 
    
         
            -
             
     | 
| 
       1631 
     | 
    
         
            -
                     
     | 
| 
       1632 
     | 
    
         
            -
             
     | 
| 
       1633 
     | 
    
         
            -
             
     | 
| 
       1634 
     | 
    
         
            -
                     
     | 
| 
       1635 
     | 
    
         
            -
                     
     | 
| 
       1636 
     | 
    
         
            -
                     
     | 
| 
       1637 
     | 
    
         
            -
             
     | 
| 
       1638 
     | 
    
         
            -
                    tag_order: tag_order,
         
     | 
| 
       1639 
     | 
    
         
            -
                    tags_color: tags_color,
         
     | 
| 
       1640 
     | 
    
         
            -
                    template: template,
         
     | 
| 
       1641 
     | 
    
         
            -
                    times: options[:t],
         
     | 
| 
       1642 
     | 
    
         
            -
                    totals: totals
         
     | 
| 
       1643 
     | 
    
         
            -
                  }
         
     | 
| 
      
 2016 
     | 
    
         
            +
                  if view.key?('from')
         
     | 
| 
      
 2017 
     | 
    
         
            +
                    date_string = view['from']
         
     | 
| 
      
 2018 
     | 
    
         
            +
                    if date_string =~ / (to|through|thru|(un)?til|-+) /
         
     | 
| 
      
 2019 
     | 
    
         
            +
                      dates = date_string.split(/ (to|through|thru|(un)?til|-+) /)
         
     | 
| 
      
 2020 
     | 
    
         
            +
                      start = wwid.chronify(dates[0], guess: :begin)
         
     | 
| 
      
 2021 
     | 
    
         
            +
                      finish = wwid.chronify(dates[2], guess: :end)
         
     | 
| 
      
 2022 
     | 
    
         
            +
                    else
         
     | 
| 
      
 2023 
     | 
    
         
            +
                      start = wwid.chronify(date_string, guess: :begin)
         
     | 
| 
      
 2024 
     | 
    
         
            +
                      finish = false
         
     | 
| 
      
 2025 
     | 
    
         
            +
                    end
         
     | 
| 
      
 2026 
     | 
    
         
            +
                    raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
         
     | 
| 
      
 2027 
     | 
    
         
            +
                    dates = [start, finish]
         
     | 
| 
      
 2028 
     | 
    
         
            +
                  end
         
     | 
| 
       1644 
2029 
     | 
    
         | 
| 
       1645 
     | 
    
         
            -
                   
     | 
| 
      
 2030 
     | 
    
         
            +
                  opts = options
         
     | 
| 
      
 2031 
     | 
    
         
            +
                  opts[:output] = output_format
         
     | 
| 
      
 2032 
     | 
    
         
            +
                  opts[:count] = count
         
     | 
| 
      
 2033 
     | 
    
         
            +
                  opts[:format] = date_format
         
     | 
| 
      
 2034 
     | 
    
         
            +
                  opts[:highlight] = options[:color]
         
     | 
| 
      
 2035 
     | 
    
         
            +
                  opts[:only_timed] = only_timed
         
     | 
| 
      
 2036 
     | 
    
         
            +
                  opts[:order] = order
         
     | 
| 
      
 2037 
     | 
    
         
            +
                  opts[:section] = section
         
     | 
| 
      
 2038 
     | 
    
         
            +
                  opts[:tag_filter] = tag_filter
         
     | 
| 
      
 2039 
     | 
    
         
            +
                  opts[:tag_order] = tag_order
         
     | 
| 
      
 2040 
     | 
    
         
            +
                  opts[:tags_color] = tags_color
         
     | 
| 
      
 2041 
     | 
    
         
            +
                  opts[:template] = template
         
     | 
| 
      
 2042 
     | 
    
         
            +
                  opts[:totals] = totals
         
     | 
| 
      
 2043 
     | 
    
         
            +
                  opts[:page_title] = page_title
         
     | 
| 
      
 2044 
     | 
    
         
            +
                  opts[:date_filter] = dates
         
     | 
| 
      
 2045 
     | 
    
         
            +
                  opts[:output] = options[:interactive] ? nil : options[:output]
         
     | 
| 
      
 2046 
     | 
    
         
            +
             
     | 
| 
      
 2047 
     | 
    
         
            +
                  Doing::Pager.page wwid.list_section(opts)
         
     | 
| 
       1646 
2048 
     | 
    
         
             
                elsif title.instance_of?(FalseClass)
         
     | 
| 
       1647 
2049 
     | 
    
         
             
                  exit_now! 'Cancelled'
         
     | 
| 
       1648 
2050 
     | 
    
         
             
                else
         
     | 
| 
       1649 
     | 
    
         
            -
                   
     | 
| 
      
 2051 
     | 
    
         
            +
                  raise Doing::Errors::InvalidView, "View #{title} not found in config"
         
     | 
| 
       1650 
2052 
     | 
    
         
             
                end
         
     | 
| 
       1651 
2053 
     | 
    
         
             
              end
         
     | 
| 
       1652 
2054 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1663,9 +2065,18 @@ command :views do |c| 
     | 
|
| 
       1663 
2065 
     | 
    
         
             
            end
         
     | 
| 
       1664 
2066 
     | 
    
         | 
| 
       1665 
2067 
     | 
    
         
             
            desc 'Move entries between sections'
         
     | 
| 
       1666 
     | 
    
         
            -
             
     | 
| 
       1667 
     | 
    
         
            -
             
     | 
| 
       1668 
     | 
    
         
            -
             
     | 
| 
      
 2068 
     | 
    
         
            +
            long_desc %(Argument can be a section name to move all entries from a section,
         
     | 
| 
      
 2069 
     | 
    
         
            +
            or start with an "@" to move entries matching a tag.
         
     | 
| 
      
 2070 
     | 
    
         
            +
             
     | 
| 
      
 2071 
     | 
    
         
            +
            Default with no argument moves items from the "#{settings['current_section']}" section to Archive.)
         
     | 
| 
      
 2072 
     | 
    
         
            +
            arg_name 'SECTION_OR_TAG'
         
     | 
| 
      
 2073 
     | 
    
         
            +
            default_value settings['current_section']
         
     | 
| 
      
 2074 
     | 
    
         
            +
            command %i[archive move] do |c|
         
     | 
| 
      
 2075 
     | 
    
         
            +
              c.example 'doing archive Currently', desc: 'Move all entries in the Currently section to Archive section'
         
     | 
| 
      
 2076 
     | 
    
         
            +
              c.example 'doing archive @done', desc: 'Move all entries tagged @done to Archive'
         
     | 
| 
      
 2077 
     | 
    
         
            +
              c.example 'doing archive --to Later @project1', desc: 'Move all entries tagged @project1 to Later section'
         
     | 
| 
      
 2078 
     | 
    
         
            +
              c.example 'doing move Later --tag project1 --to Currently', desc: 'Move entries in Later tagged @project1 to Currently (move is an alias for archive)'
         
     | 
| 
      
 2079 
     | 
    
         
            +
             
     | 
| 
       1669 
2080 
     | 
    
         
             
              c.desc 'How many items to keep (ignored if archiving by tag or search)'
         
     | 
| 
       1670 
2081 
     | 
    
         
             
              c.arg_name 'X'
         
     | 
| 
       1671 
2082 
     | 
    
         
             
              c.flag %i[k keep], must_match: /^\d+$/, type: Integer
         
     | 
| 
         @@ -1683,7 +2094,7 @@ command :archive do |c| 
     | 
|
| 
       1683 
2094 
     | 
    
         | 
| 
       1684 
2095 
     | 
    
         
             
              c.desc 'Tag boolean (AND|OR|NOT)'
         
     | 
| 
       1685 
2096 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       1686 
     | 
    
         
            -
              c.flag [:bool], must_match:  
     | 
| 
      
 2097 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
       1687 
2098 
     | 
    
         | 
| 
       1688 
2099 
     | 
    
         
             
              c.desc 'Search filter'
         
     | 
| 
       1689 
2100 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
         @@ -1696,7 +2107,7 @@ command :archive do |c| 
     | 
|
| 
       1696 
2107 
     | 
    
         | 
| 
       1697 
2108 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       1698 
2109 
     | 
    
         
             
                if args.empty?
         
     | 
| 
       1699 
     | 
    
         
            -
                  section =  
     | 
| 
      
 2110 
     | 
    
         
            +
                  section = settings['current_section']
         
     | 
| 
       1700 
2111 
     | 
    
         
             
                  tags = []
         
     | 
| 
       1701 
2112 
     | 
    
         
             
                elsif args[0] =~ /^all/i
         
     | 
| 
       1702 
2113 
     | 
    
         
             
                  section = 'all'
         
     | 
| 
         @@ -1708,34 +2119,25 @@ command :archive do |c| 
     | 
|
| 
       1708 
2119 
     | 
    
         
             
                  tags = args.length > 1 ? args[1..].map { |t| t.sub(/^@/, '').strip } : []
         
     | 
| 
       1709 
2120 
     | 
    
         
             
                end
         
     | 
| 
       1710 
2121 
     | 
    
         | 
| 
       1711 
     | 
    
         
            -
                 
     | 
| 
      
 2122 
     | 
    
         
            +
                raise Doing::Errors::InvalidArgument, '--keep and --count can not be used together' if options[:keep] && options[:count]
         
     | 
| 
       1712 
2123 
     | 
    
         | 
| 
       1713 
     | 
    
         
            -
                tags.concat(options[:tag]. 
     | 
| 
      
 2124 
     | 
    
         
            +
                tags.concat(options[:tag].to_tags) if options[:tag]
         
     | 
| 
      
 2125 
     | 
    
         
            +
             
     | 
| 
      
 2126 
     | 
    
         
            +
                opts = options
         
     | 
| 
      
 2127 
     | 
    
         
            +
                opts[:bool] = options[:bool].normalize_bool
         
     | 
| 
      
 2128 
     | 
    
         
            +
                opts[:destination] = options[:to]
         
     | 
| 
      
 2129 
     | 
    
         
            +
                opts[:tags] = tags
         
     | 
| 
       1714 
2130 
     | 
    
         | 
| 
       1715 
     | 
    
         
            -
                options[:bool] = case options[:bool]
         
     | 
| 
       1716 
     | 
    
         
            -
                                 when /(and|all)/i
         
     | 
| 
       1717 
     | 
    
         
            -
                                  'AND'
         
     | 
| 
       1718 
     | 
    
         
            -
                                 when /(any|or)/i
         
     | 
| 
       1719 
     | 
    
         
            -
                                  'OR'
         
     | 
| 
       1720 
     | 
    
         
            -
                                 when /(not|none)/i
         
     | 
| 
       1721 
     | 
    
         
            -
                                  'NOT'
         
     | 
| 
       1722 
     | 
    
         
            -
                                 else
         
     | 
| 
       1723 
     | 
    
         
            -
                                  'AND'
         
     | 
| 
       1724 
     | 
    
         
            -
                                 end
         
     | 
| 
       1725 
     | 
    
         
            -
                opts = {
         
     | 
| 
       1726 
     | 
    
         
            -
                  before: options[:before],
         
     | 
| 
       1727 
     | 
    
         
            -
                  bool: options[:bool],
         
     | 
| 
       1728 
     | 
    
         
            -
                  destination: options[:to],
         
     | 
| 
       1729 
     | 
    
         
            -
                  keep: options[:keep],
         
     | 
| 
       1730 
     | 
    
         
            -
                  search: options[:search],
         
     | 
| 
       1731 
     | 
    
         
            -
                  tags: tags
         
     | 
| 
       1732 
     | 
    
         
            -
                }
         
     | 
| 
       1733 
2131 
     | 
    
         
             
                wwid.archive(section, opts)
         
     | 
| 
       1734 
2132 
     | 
    
         
             
              end
         
     | 
| 
       1735 
2133 
     | 
    
         
             
            end
         
     | 
| 
       1736 
2134 
     | 
    
         | 
| 
       1737 
2135 
     | 
    
         
             
            desc 'Move entries to archive file'
         
     | 
| 
       1738 
2136 
     | 
    
         
             
            command :rotate do |c|
         
     | 
| 
      
 2137 
     | 
    
         
            +
              c.example 'doing rotate', desc: 'Move all entries in doing file to a dated secondary file'
         
     | 
| 
      
 2138 
     | 
    
         
            +
              c.example 'doing rotate --section Archive --keep 10', desc: 'Move entries in the Archive section to a secondary file, keeping the most recent 10 entries'
         
     | 
| 
      
 2139 
     | 
    
         
            +
              c.example 'doing rotate --tag project1,done --bool AND', desc: 'Move entries tagged @project1 and @done to a secondary file'
         
     | 
| 
      
 2140 
     | 
    
         
            +
             
     | 
| 
       1739 
2141 
     | 
    
         
             
              c.desc 'How many items to keep in each section (most recent)'
         
     | 
| 
       1740 
2142 
     | 
    
         
             
              c.arg_name 'X'
         
     | 
| 
       1741 
2143 
     | 
    
         
             
              c.flag %i[k keep], must_match: /^\d+$/, type: Integer
         
     | 
| 
         @@ -1750,7 +2152,7 @@ command :rotate do |c| 
     | 
|
| 
       1750 
2152 
     | 
    
         | 
| 
       1751 
2153 
     | 
    
         
             
              c.desc 'Tag boolean (AND|OR|NOT)'
         
     | 
| 
       1752 
2154 
     | 
    
         
             
              c.arg_name 'BOOLEAN'
         
     | 
| 
       1753 
     | 
    
         
            -
              c.flag [:bool], must_match:  
     | 
| 
      
 2155 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
       1754 
2156 
     | 
    
         | 
| 
       1755 
2157 
     | 
    
         
             
              c.desc 'Search filter'
         
     | 
| 
       1756 
2158 
     | 
    
         
             
              c.arg_name 'QUERY'
         
     | 
| 
         @@ -1766,23 +2168,14 @@ command :rotate do |c| 
     | 
|
| 
       1766 
2168 
     | 
    
         
             
                  options[:section] = wwid.guess_section(options[:section])
         
     | 
| 
       1767 
2169 
     | 
    
         
             
                end
         
     | 
| 
       1768 
2170 
     | 
    
         | 
| 
       1769 
     | 
    
         
            -
                options[:bool] =  
     | 
| 
       1770 
     | 
    
         
            -
                                 when /(and|all)/i
         
     | 
| 
       1771 
     | 
    
         
            -
                                  'AND'
         
     | 
| 
       1772 
     | 
    
         
            -
                                 when /(any|or)/i
         
     | 
| 
       1773 
     | 
    
         
            -
                                  'OR'
         
     | 
| 
       1774 
     | 
    
         
            -
                                 when /(not|none)/i
         
     | 
| 
       1775 
     | 
    
         
            -
                                  'NOT'
         
     | 
| 
       1776 
     | 
    
         
            -
                                 else
         
     | 
| 
       1777 
     | 
    
         
            -
                                  'AND'
         
     | 
| 
       1778 
     | 
    
         
            -
                                 end
         
     | 
| 
      
 2171 
     | 
    
         
            +
                options[:bool] = options[:bool].normalize_bool
         
     | 
| 
       1779 
2172 
     | 
    
         | 
| 
       1780 
2173 
     | 
    
         
             
                wwid.rotate(options)
         
     | 
| 
       1781 
2174 
     | 
    
         
             
              end
         
     | 
| 
       1782 
2175 
     | 
    
         
             
            end
         
     | 
| 
       1783 
2176 
     | 
    
         | 
| 
       1784 
2177 
     | 
    
         
             
            desc 'Open the "doing" file in an editor'
         
     | 
| 
       1785 
     | 
    
         
            -
            long_desc "`doing open` defaults to using the editor_app setting in #{ 
     | 
| 
      
 2178 
     | 
    
         
            +
            long_desc "`doing open` defaults to using the editor_app setting in #{config.config_file} (#{settings.key?('editor_app') ? settings['editor_app'] : 'not set'})."
         
     | 
| 
       1786 
2179 
     | 
    
         
             
            command :open do |c|
         
     | 
| 
       1787 
2180 
     | 
    
         
             
              if `uname` =~ /Darwin/
         
     | 
| 
       1788 
2181 
     | 
    
         
             
                c.desc 'Open with app name'
         
     | 
| 
         @@ -1793,8 +2186,6 @@ command :open do |c| 
     | 
|
| 
       1793 
2186 
     | 
    
         
             
                c.arg_name 'BUNDLE_ID'
         
     | 
| 
       1794 
2187 
     | 
    
         
             
                c.flag %i[b bundle_id]
         
     | 
| 
       1795 
2188 
     | 
    
         
             
              end
         
     | 
| 
       1796 
     | 
    
         
            -
              c.desc "Open with $EDITOR (#{ENV['EDITOR']})"
         
     | 
| 
       1797 
     | 
    
         
            -
              c.switch %i[e editor], negatable: false, default_value: false
         
     | 
| 
       1798 
2189 
     | 
    
         | 
| 
       1799 
2190 
     | 
    
         
             
              c.action do |_global_options, options, _args|
         
     | 
| 
       1800 
2191 
     | 
    
         
             
                params = options.dup
         
     | 
| 
         @@ -1806,30 +2197,54 @@ command :open do |c| 
     | 
|
| 
       1806 
2197 
     | 
    
         
             
                    system %(open -a "#{options[:a]}" "#{File.expand_path(wwid.doing_file)}")
         
     | 
| 
       1807 
2198 
     | 
    
         
             
                  elsif options[:bundle_id]
         
     | 
| 
       1808 
2199 
     | 
    
         
             
                    system %(open -b "#{options[:b]}" "#{File.expand_path(wwid.doing_file)}")
         
     | 
| 
       1809 
     | 
    
         
            -
                  elsif  
     | 
| 
       1810 
     | 
    
         
            -
                     
     | 
| 
       1811 
     | 
    
         
            -
             
     | 
| 
       1812 
     | 
    
         
            -
             
     | 
| 
       1813 
     | 
    
         
            -
             
     | 
| 
       1814 
     | 
    
         
            -
             
     | 
| 
      
 2200 
     | 
    
         
            +
                  elsif Doing::Util.find_default_editor('doing_file')
         
     | 
| 
      
 2201 
     | 
    
         
            +
                    editor = Doing::Util.find_default_editor('doing_file')
         
     | 
| 
      
 2202 
     | 
    
         
            +
                    if Doing::Util.exec_available(editor)
         
     | 
| 
      
 2203 
     | 
    
         
            +
                      system %(#{editor} "#{File.expand_path(wwid.doing_file)}")
         
     | 
| 
      
 2204 
     | 
    
         
            +
                    else
         
     | 
| 
      
 2205 
     | 
    
         
            +
                      system %(open -a "#{editor}" "#{File.expand_path(wwid.doing_file)}")
         
     | 
| 
      
 2206 
     | 
    
         
            +
                    end
         
     | 
| 
       1815 
2207 
     | 
    
         
             
                  else
         
     | 
| 
       1816 
2208 
     | 
    
         
             
                    system %(open "#{File.expand_path(wwid.doing_file)}")
         
     | 
| 
       1817 
2209 
     | 
    
         
             
                  end
         
     | 
| 
       1818 
     | 
    
         
            -
             
     | 
| 
       1819 
2210 
     | 
    
         
             
                else
         
     | 
| 
       1820 
     | 
    
         
            -
                   
     | 
| 
      
 2211 
     | 
    
         
            +
                  raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' if Doing::Util.default_editor.nil?
         
     | 
| 
       1821 
2212 
     | 
    
         | 
| 
       1822 
     | 
    
         
            -
                  system %( 
     | 
| 
      
 2213 
     | 
    
         
            +
                  system %(#{Doing::Util.default_editor} "#{File.expand_path(wwid.doing_file)}")
         
     | 
| 
       1823 
2214 
     | 
    
         
             
                end
         
     | 
| 
       1824 
2215 
     | 
    
         
             
              end
         
     | 
| 
       1825 
2216 
     | 
    
         
             
            end
         
     | 
| 
       1826 
2217 
     | 
    
         | 
| 
       1827 
     | 
    
         
            -
            desc 'Edit the configuration file'
         
     | 
| 
      
 2218 
     | 
    
         
            +
            desc 'Edit the configuration file or output a value from it'
         
     | 
| 
      
 2219 
     | 
    
         
            +
            long_desc %(Run without arguments, `doing config` opens your `.doingrc` in an editor.
         
     | 
| 
      
 2220 
     | 
    
         
            +
            If local configurations are found in the path between the current directory
         
     | 
| 
      
 2221 
     | 
    
         
            +
            and `~/.doingrc`, a menu will allow you to select which to open in the editor.
         
     | 
| 
      
 2222 
     | 
    
         
            +
             
     | 
| 
      
 2223 
     | 
    
         
            +
            It will use the editor defined in `config_editor_app`, or one specified with `--editor`.
         
     | 
| 
      
 2224 
     | 
    
         
            +
             
     | 
| 
      
 2225 
     | 
    
         
            +
            Use `doing config -d` to output the configuration to the terminal, and
         
     | 
| 
      
 2226 
     | 
    
         
            +
            provide a dot-separated key path to get a specific value. Shows the current value
         
     | 
| 
      
 2227 
     | 
    
         
            +
            including keys/overrides set by local configs.)
         
     | 
| 
      
 2228 
     | 
    
         
            +
            arg_name 'KEY_PATH'
         
     | 
| 
       1828 
2229 
     | 
    
         
             
            command :config do |c|
         
     | 
| 
      
 2230 
     | 
    
         
            +
              c.example 'doing config', desc: "Open an active configuration in #{Doing::Util.find_default_editor('config')}"
         
     | 
| 
      
 2231 
     | 
    
         
            +
              c.example 'doing config -d doing_file', desc: 'Output the value of a config key as YAML'
         
     | 
| 
      
 2232 
     | 
    
         
            +
              c.example 'doing config -d plugins.say.say_voice -o json', desc: 'Output the value of a key path as JSON'
         
     | 
| 
      
 2233 
     | 
    
         
            +
             
     | 
| 
       1829 
2234 
     | 
    
         
             
              c.desc 'Editor to use'
         
     | 
| 
       1830 
2235 
     | 
    
         
             
              c.arg_name 'EDITOR'
         
     | 
| 
       1831 
2236 
     | 
    
         
             
              c.flag %i[e editor], default_value: nil
         
     | 
| 
       1832 
2237 
     | 
    
         | 
| 
      
 2238 
     | 
    
         
            +
              c.desc 'Show a config key value based on arguments. Separate key paths with colons or dots, e.g. "export_templates.html". Empty arguments outputs the entire config.'
         
     | 
| 
      
 2239 
     | 
    
         
            +
              c.switch %i[d dump]
         
     | 
| 
      
 2240 
     | 
    
         
            +
             
     | 
| 
      
 2241 
     | 
    
         
            +
              c.desc 'Format for --dump (json|yaml|raw)'
         
     | 
| 
      
 2242 
     | 
    
         
            +
              c.arg_name 'FORMAT'
         
     | 
| 
      
 2243 
     | 
    
         
            +
              c.flag %i[o output], default_value: 'yaml', must_match: /^(?:y(?:aml)?|j(?:son)?|r(?:aw)?)$/
         
     | 
| 
      
 2244 
     | 
    
         
            +
             
     | 
| 
      
 2245 
     | 
    
         
            +
              c.desc 'Update config file with missing configuration options'
         
     | 
| 
      
 2246 
     | 
    
         
            +
              c.switch %i[u update], default_value: false, negatable: false
         
     | 
| 
      
 2247 
     | 
    
         
            +
             
     | 
| 
       1833 
2248 
     | 
    
         
             
              if `uname` =~ /Darwin/
         
     | 
| 
       1834 
2249 
     | 
    
         
             
                c.desc 'Application to use'
         
     | 
| 
       1835 
2250 
     | 
    
         
             
                c.arg_name 'APP_NAME'
         
     | 
| 
         @@ -1839,31 +2254,84 @@ command :config do |c| 
     | 
|
| 
       1839 
2254 
     | 
    
         
             
                c.arg_name 'BUNDLE_ID'
         
     | 
| 
       1840 
2255 
     | 
    
         
             
                c.flag [:b]
         
     | 
| 
       1841 
2256 
     | 
    
         | 
| 
       1842 
     | 
    
         
            -
                c.desc "Use the config_editor_app defined in ~/.doingrc (#{ 
     | 
| 
      
 2257 
     | 
    
         
            +
                c.desc "Use the config_editor_app defined in ~/.doingrc (#{settings.key?('config_editor_app') ? settings['config_editor_app'] : 'config_editor_app not set'})"
         
     | 
| 
       1843 
2258 
     | 
    
         
             
                c.switch [:x]
         
     | 
| 
       1844 
2259 
     | 
    
         
             
              end
         
     | 
| 
       1845 
2260 
     | 
    
         | 
| 
       1846 
     | 
    
         
            -
              c.action do |_global_options, options,  
     | 
| 
      
 2261 
     | 
    
         
            +
              c.action do |_global_options, options, args|
         
     | 
| 
      
 2262 
     | 
    
         
            +
                if options[:update]
         
     | 
| 
      
 2263 
     | 
    
         
            +
                  config.configure({rewrite: true, ignore_local: true})
         
     | 
| 
      
 2264 
     | 
    
         
            +
                  # Doing.logger.warn("Config file rewritten: #{config.config_file}")
         
     | 
| 
      
 2265 
     | 
    
         
            +
                  return
         
     | 
| 
      
 2266 
     | 
    
         
            +
                end
         
     | 
| 
      
 2267 
     | 
    
         
            +
             
     | 
| 
      
 2268 
     | 
    
         
            +
                if options[:dump]
         
     | 
| 
      
 2269 
     | 
    
         
            +
                  keypath = args.join('.')
         
     | 
| 
      
 2270 
     | 
    
         
            +
                  cfg = config.value_for_key(keypath)
         
     | 
| 
      
 2271 
     | 
    
         
            +
             
     | 
| 
      
 2272 
     | 
    
         
            +
                  if cfg
         
     | 
| 
      
 2273 
     | 
    
         
            +
                    $stdout.puts case options[:output]
         
     | 
| 
      
 2274 
     | 
    
         
            +
                                 when /^j/
         
     | 
| 
      
 2275 
     | 
    
         
            +
                                   JSON.pretty_generate(cfg)
         
     | 
| 
      
 2276 
     | 
    
         
            +
                                 when /^r/
         
     | 
| 
      
 2277 
     | 
    
         
            +
                                   cfg
         
     | 
| 
      
 2278 
     | 
    
         
            +
                                 else
         
     | 
| 
      
 2279 
     | 
    
         
            +
                                   # cfg = { last_key => cfg } unless last_key.nil?
         
     | 
| 
      
 2280 
     | 
    
         
            +
                                   YAML.dump(cfg)
         
     | 
| 
      
 2281 
     | 
    
         
            +
                                 end
         
     | 
| 
      
 2282 
     | 
    
         
            +
                  else
         
     | 
| 
      
 2283 
     | 
    
         
            +
                    Doing.logger.log_now(:error, 'Config:', "Key #{keypath} not found")
         
     | 
| 
      
 2284 
     | 
    
         
            +
                  end
         
     | 
| 
      
 2285 
     | 
    
         
            +
                  Doing.logger.output_results
         
     | 
| 
      
 2286 
     | 
    
         
            +
                  return
         
     | 
| 
      
 2287 
     | 
    
         
            +
                end
         
     | 
| 
      
 2288 
     | 
    
         
            +
             
     | 
| 
      
 2289 
     | 
    
         
            +
                if config.additional_configs.count.positive?
         
     | 
| 
      
 2290 
     | 
    
         
            +
                  choices = [config.config_file]
         
     | 
| 
      
 2291 
     | 
    
         
            +
                  choices.concat(config.additional_configs)
         
     | 
| 
      
 2292 
     | 
    
         
            +
                  res = wwid.choose_from(choices.uniq.sort.reverse, sorted: false, prompt: 'Local configs found, select which to edit > ')
         
     | 
| 
      
 2293 
     | 
    
         
            +
             
     | 
| 
      
 2294 
     | 
    
         
            +
                  raise Doing::Errors::UserCancelled, 'Cancelled' unless res
         
     | 
| 
      
 2295 
     | 
    
         
            +
             
     | 
| 
      
 2296 
     | 
    
         
            +
                  config_file = res.strip || config.config_file
         
     | 
| 
      
 2297 
     | 
    
         
            +
                else
         
     | 
| 
      
 2298 
     | 
    
         
            +
                  config_file = config.config_file
         
     | 
| 
      
 2299 
     | 
    
         
            +
                end
         
     | 
| 
      
 2300 
     | 
    
         
            +
             
     | 
| 
       1847 
2301 
     | 
    
         
             
                if `uname` =~ /Darwin/
         
     | 
| 
       1848 
2302 
     | 
    
         
             
                  if options[:x]
         
     | 
| 
       1849 
     | 
    
         
            -
                     
     | 
| 
      
 2303 
     | 
    
         
            +
                    editor = Doing::Util.find_default_editor('config')
         
     | 
| 
      
 2304 
     | 
    
         
            +
                    if editor
         
     | 
| 
      
 2305 
     | 
    
         
            +
                      if Doing::Util.exec_available(editor)
         
     | 
| 
      
 2306 
     | 
    
         
            +
                        system %(#{editor} "#{config_file}")
         
     | 
| 
      
 2307 
     | 
    
         
            +
                      else
         
     | 
| 
      
 2308 
     | 
    
         
            +
                        `open -a "#{editor}" "#{config_file}"`
         
     | 
| 
      
 2309 
     | 
    
         
            +
                      end
         
     | 
| 
      
 2310 
     | 
    
         
            +
                    else
         
     | 
| 
      
 2311 
     | 
    
         
            +
                      raise Doing::Errors::InvalidArgument, 'No viable editor found in config or environment.'
         
     | 
| 
      
 2312 
     | 
    
         
            +
                    end
         
     | 
| 
       1850 
2313 
     | 
    
         
             
                  elsif options[:a] || options[:b]
         
     | 
| 
       1851 
2314 
     | 
    
         
             
                    if options[:a]
         
     | 
| 
       1852 
     | 
    
         
            -
                      `open -a "#{options[:a]}" "#{ 
     | 
| 
      
 2315 
     | 
    
         
            +
                      `open -a "#{options[:a]}" "#{config_file}"`
         
     | 
| 
       1853 
2316 
     | 
    
         
             
                    elsif options[:b]
         
     | 
| 
       1854 
     | 
    
         
            -
                      `open -b #{options[:b]} "#{ 
     | 
| 
      
 2317 
     | 
    
         
            +
                      `open -b #{options[:b]} "#{config_file}"`
         
     | 
| 
       1855 
2318 
     | 
    
         
             
                    end
         
     | 
| 
       1856 
2319 
     | 
    
         
             
                  else
         
     | 
| 
       1857 
     | 
    
         
            -
                     
     | 
| 
      
 2320 
     | 
    
         
            +
                    editor = options[:e] || Doing::Util.find_default_editor('config')
         
     | 
| 
       1858 
2321 
     | 
    
         | 
| 
       1859 
     | 
    
         
            -
                    editor  
     | 
| 
       1860 
     | 
    
         
            -
             
     | 
| 
      
 2322 
     | 
    
         
            +
                    raise Doing::Errors::MissingEditor, 'No viable editor defined in config or environment' unless editor
         
     | 
| 
      
 2323 
     | 
    
         
            +
             
     | 
| 
      
 2324 
     | 
    
         
            +
                    if Doing::Util.exec_available(editor)
         
     | 
| 
      
 2325 
     | 
    
         
            +
                      system %(#{editor} "#{config_file}")
         
     | 
| 
      
 2326 
     | 
    
         
            +
                    else
         
     | 
| 
      
 2327 
     | 
    
         
            +
                      `open -a "#{editor}" "#{config_file}"`
         
     | 
| 
      
 2328 
     | 
    
         
            +
                    end
         
     | 
| 
       1861 
2329 
     | 
    
         
             
                  end
         
     | 
| 
       1862 
2330 
     | 
    
         
             
                else
         
     | 
| 
       1863 
     | 
    
         
            -
                   
     | 
| 
      
 2331 
     | 
    
         
            +
                  editor = options[:e] || Doing::Util.default_editor
         
     | 
| 
      
 2332 
     | 
    
         
            +
                  raise Doing::Errors::MissingEditor, 'No EDITOR variable defined in environment' unless editor && Doing::Util.exec_available(editor)
         
     | 
| 
       1864 
2333 
     | 
    
         | 
| 
       1865 
     | 
    
         
            -
                  editor  
     | 
| 
       1866 
     | 
    
         
            -
                  system %(#{editor} "#{wwid.config_file}")
         
     | 
| 
      
 2334 
     | 
    
         
            +
                  system %(#{editor} "#{config_file}")
         
     | 
| 
       1867 
2335 
     | 
    
         
             
                end
         
     | 
| 
       1868 
2336 
     | 
    
         
             
              end
         
     | 
| 
       1869 
2337 
     | 
    
         
             
            end
         
     | 
| 
         @@ -1881,12 +2349,19 @@ command :undo do |c| 
     | 
|
| 
       1881 
2349 
     | 
    
         
             
            end
         
     | 
| 
       1882 
2350 
     | 
    
         | 
| 
       1883 
2351 
     | 
    
         
             
            desc 'Import entries from an external source'
         
     | 
| 
       1884 
     | 
    
         
            -
            long_desc  
     | 
| 
      
 2352 
     | 
    
         
            +
            long_desc "Imports entries from other sources. Available plugins: #{Doing::Plugins.plugin_names(type: :import, separator: ', ')}"
         
     | 
| 
       1885 
2353 
     | 
    
         
             
            arg_name 'PATH'
         
     | 
| 
       1886 
2354 
     | 
    
         
             
            command :import do |c|
         
     | 
| 
       1887 
     | 
    
         
            -
              c.desc  
     | 
| 
      
 2355 
     | 
    
         
            +
              c.desc "Import type (#{Doing::Plugins.plugin_names(type: :import)})"
         
     | 
| 
       1888 
2356 
     | 
    
         
             
              c.arg_name 'TYPE'
         
     | 
| 
       1889 
     | 
    
         
            -
              c.flag :type, default_value: ' 
     | 
| 
      
 2357 
     | 
    
         
            +
              c.flag :type, default_value: 'doing'
         
     | 
| 
      
 2358 
     | 
    
         
            +
             
     | 
| 
      
 2359 
     | 
    
         
            +
              c.desc 'Only import items matching search. Surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
         
     | 
| 
      
 2360 
     | 
    
         
            +
              c.arg_name 'QUERY'
         
     | 
| 
      
 2361 
     | 
    
         
            +
              c.flag [:search]
         
     | 
| 
      
 2362 
     | 
    
         
            +
             
     | 
| 
      
 2363 
     | 
    
         
            +
              c.desc 'Only import items with recorded time intervals'
         
     | 
| 
      
 2364 
     | 
    
         
            +
              c.switch [:only_timed], default_value: false, negatable: false
         
     | 
| 
       1890 
2365 
     | 
    
         | 
| 
       1891 
2366 
     | 
    
         
             
              c.desc 'Target section'
         
     | 
| 
       1892 
2367 
     | 
    
         
             
              c.arg_name 'NAME'
         
     | 
| 
         @@ -1903,53 +2378,67 @@ command :import do |c| 
     | 
|
| 
       1903 
2378 
     | 
    
         
             
              c.arg_name 'PREFIX'
         
     | 
| 
       1904 
2379 
     | 
    
         
             
              c.flag :prefix
         
     | 
| 
       1905 
2380 
     | 
    
         | 
| 
      
 2381 
     | 
    
         
            +
              c.desc 'Import entries older than date'
         
     | 
| 
      
 2382 
     | 
    
         
            +
              c.arg_name 'DATE_STRING'
         
     | 
| 
      
 2383 
     | 
    
         
            +
              c.flag [:before]
         
     | 
| 
      
 2384 
     | 
    
         
            +
             
     | 
| 
      
 2385 
     | 
    
         
            +
              c.desc 'Import entries newer than date'
         
     | 
| 
      
 2386 
     | 
    
         
            +
              c.arg_name 'DATE_STRING'
         
     | 
| 
      
 2387 
     | 
    
         
            +
              c.flag [:after]
         
     | 
| 
      
 2388 
     | 
    
         
            +
             
     | 
| 
      
 2389 
     | 
    
         
            +
              c.desc %(
         
     | 
| 
      
 2390 
     | 
    
         
            +
                Date range to import. Date range argument should be quoted. Date specifications can be natural language.
         
     | 
| 
      
 2391 
     | 
    
         
            +
                To specify a range, use "to" or "through": `--from "monday to friday"` or `--from 10/1 to 10/31`.
         
     | 
| 
      
 2392 
     | 
    
         
            +
                Has no effect unless the import plugin has implemented date range filtering.
         
     | 
| 
      
 2393 
     | 
    
         
            +
              )
         
     | 
| 
      
 2394 
     | 
    
         
            +
              c.arg_name 'DATE_OR_RANGE'
         
     | 
| 
      
 2395 
     | 
    
         
            +
              c.flag %i[f from]
         
     | 
| 
      
 2396 
     | 
    
         
            +
             
     | 
| 
       1906 
2397 
     | 
    
         
             
              c.desc 'Allow entries that overlap existing times'
         
     | 
| 
       1907 
2398 
     | 
    
         
             
              c.switch [:overlap], negatable: true
         
     | 
| 
       1908 
2399 
     | 
    
         | 
| 
       1909 
2400 
     | 
    
         
             
              c.action do |_global_options, options, args|
         
     | 
| 
       1910 
2401 
     | 
    
         | 
| 
       1911 
2402 
     | 
    
         
             
                if options[:section]
         
     | 
| 
       1912 
     | 
    
         
            -
                  section = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
       1913 
     | 
    
         
            -
                else
         
     | 
| 
       1914 
     | 
    
         
            -
                  section = wwid.config['current_section']
         
     | 
| 
      
 2403 
     | 
    
         
            +
                  options[:section] = wwid.guess_section(options[:section]) || options[:section].cap_first
         
     | 
| 
       1915 
2404 
     | 
    
         
             
                end
         
     | 
| 
       1916 
2405 
     | 
    
         | 
| 
       1917 
     | 
    
         
            -
                if options[: 
     | 
| 
       1918 
     | 
    
         
            -
                   
     | 
| 
       1919 
     | 
    
         
            -
             
     | 
| 
       1920 
     | 
    
         
            -
             
     | 
| 
       1921 
     | 
    
         
            -
             
     | 
| 
       1922 
     | 
    
         
            -
             
     | 
| 
       1923 
     | 
    
         
            -
             
     | 
| 
       1924 
     | 
    
         
            -
             
     | 
| 
       1925 
     | 
    
         
            -
                     
     | 
| 
       1926 
     | 
    
         
            -
                    wwid.import_timing(path, options)
         
     | 
| 
       1927 
     | 
    
         
            -
                    wwid.write(wwid.doing_file)
         
     | 
| 
      
 2406 
     | 
    
         
            +
                if options[:from]
         
     | 
| 
      
 2407 
     | 
    
         
            +
                  date_string = options[:from]
         
     | 
| 
      
 2408 
     | 
    
         
            +
                  if date_string =~ / (to|through|thru|(un)?til|-+) /
         
     | 
| 
      
 2409 
     | 
    
         
            +
                    dates = date_string.split(/ (to|through|thru|(un)?til|-+) /)
         
     | 
| 
      
 2410 
     | 
    
         
            +
                    start = wwid.chronify(dates[0], guess: :begin)
         
     | 
| 
      
 2411 
     | 
    
         
            +
                    finish = wwid.chronify(dates[2], guess: :end)
         
     | 
| 
      
 2412 
     | 
    
         
            +
                  else
         
     | 
| 
      
 2413 
     | 
    
         
            +
                    start = wwid.chronify(date_string, guess: :begin)
         
     | 
| 
      
 2414 
     | 
    
         
            +
                    finish = false
         
     | 
| 
       1928 
2415 
     | 
    
         
             
                  end
         
     | 
| 
      
 2416 
     | 
    
         
            +
                  raise Doing::Errors::InvalidTimeExpression, 'Unrecognized date string' unless start
         
     | 
| 
      
 2417 
     | 
    
         
            +
                  dates = [start, finish]
         
     | 
| 
      
 2418 
     | 
    
         
            +
                end
         
     | 
| 
      
 2419 
     | 
    
         
            +
             
     | 
| 
      
 2420 
     | 
    
         
            +
                if options[:type] =~ Doing::Plugins.plugin_regex(type: :import)
         
     | 
| 
      
 2421 
     | 
    
         
            +
                  options[:no_overlap] = !options[:overlap]
         
     | 
| 
      
 2422 
     | 
    
         
            +
                  options[:date_filter] = dates
         
     | 
| 
      
 2423 
     | 
    
         
            +
                  wwid.import(args, options)
         
     | 
| 
      
 2424 
     | 
    
         
            +
                  wwid.write(wwid.doing_file)
         
     | 
| 
       1929 
2425 
     | 
    
         
             
                else
         
     | 
| 
       1930 
     | 
    
         
            -
                   
     | 
| 
      
 2426 
     | 
    
         
            +
                  raise Doing::Errors::InvalidPluginType, "Invalid import type: #{options[:type]}"
         
     | 
| 
       1931 
2427 
     | 
    
         
             
                end
         
     | 
| 
       1932 
2428 
     | 
    
         
             
              end
         
     | 
| 
       1933 
2429 
     | 
    
         
             
            end
         
     | 
| 
       1934 
2430 
     | 
    
         | 
| 
       1935 
2431 
     | 
    
         
             
            pre do |global, _command, _options, _args|
         
     | 
| 
       1936 
     | 
    
         
            -
               
     | 
| 
       1937 
     | 
    
         
            -
                wwid.config_file = global[:config_file]
         
     | 
| 
       1938 
     | 
    
         
            -
                wwid.configure({ ignore_local: true })
         
     | 
| 
       1939 
     | 
    
         
            -
                # wwid.results.push("Override config file #{wwid.config_file}")
         
     | 
| 
       1940 
     | 
    
         
            -
              end
         
     | 
| 
      
 2432 
     | 
    
         
            +
              # global[:pager] ||= settings['paginate']
         
     | 
| 
       1941 
2433 
     | 
    
         | 
| 
       1942 
     | 
    
         
            -
               
     | 
| 
       1943 
     | 
    
         
            -
                wwid.init_doing_file(global[:doing_file])
         
     | 
| 
       1944 
     | 
    
         
            -
              else
         
     | 
| 
       1945 
     | 
    
         
            -
                wwid.init_doing_file
         
     | 
| 
       1946 
     | 
    
         
            -
              end
         
     | 
| 
       1947 
     | 
    
         
            -
             
     | 
| 
       1948 
     | 
    
         
            -
              wwid.auto_tag = !global[:noauto]
         
     | 
| 
       1949 
     | 
    
         
            -
             
     | 
| 
       1950 
     | 
    
         
            -
              wwid.config[:include_notes] = false unless global[:notes]
         
     | 
| 
      
 2434 
     | 
    
         
            +
              Doing::Pager.paginate = global[:pager]
         
     | 
| 
       1951 
2435 
     | 
    
         | 
| 
       1952 
2436 
     | 
    
         
             
              $stdout.puts "doing v#{Doing::VERSION}" if global[:version]
         
     | 
| 
      
 2437 
     | 
    
         
            +
              unless STDOUT.isatty
         
     | 
| 
      
 2438 
     | 
    
         
            +
                Doing::Color::coloring = global[:pager] ? global[:color] : false
         
     | 
| 
      
 2439 
     | 
    
         
            +
              else
         
     | 
| 
      
 2440 
     | 
    
         
            +
                Doing::Color::coloring = global[:color]
         
     | 
| 
      
 2441 
     | 
    
         
            +
              end
         
     | 
| 
       1953 
2442 
     | 
    
         | 
| 
       1954 
2443 
     | 
    
         
             
              # Return true to proceed; false to abort and not call the
         
     | 
| 
       1955 
2444 
     | 
    
         
             
              # chosen command
         
     | 
| 
         @@ -1958,20 +2447,57 @@ pre do |global, _command, _options, _args| 
     | 
|
| 
       1958 
2447 
     | 
    
         
             
              true
         
     | 
| 
       1959 
2448 
     | 
    
         
             
            end
         
     | 
| 
       1960 
2449 
     | 
    
         | 
| 
      
 2450 
     | 
    
         
            +
            on_error do |exception|
         
     | 
| 
      
 2451 
     | 
    
         
            +
              # if exception.kind_of?(SystemExit)
         
     | 
| 
      
 2452 
     | 
    
         
            +
              #   false
         
     | 
| 
      
 2453 
     | 
    
         
            +
              # else
         
     | 
| 
      
 2454 
     | 
    
         
            +
              #   p exception.inspect
         
     | 
| 
      
 2455 
     | 
    
         
            +
              #   Doing.logger.output_results
         
     | 
| 
      
 2456 
     | 
    
         
            +
              #   true
         
     | 
| 
      
 2457 
     | 
    
         
            +
              # end
         
     | 
| 
      
 2458 
     | 
    
         
            +
              false
         
     | 
| 
      
 2459 
     | 
    
         
            +
            end
         
     | 
| 
      
 2460 
     | 
    
         
            +
             
     | 
| 
       1961 
2461 
     | 
    
         
             
            post do |global, _command, _options, _args|
         
     | 
| 
       1962 
2462 
     | 
    
         
             
              # Use skips_post before a command to skip this
         
     | 
| 
       1963 
2463 
     | 
    
         
             
              # block on that command only
         
     | 
| 
      
 2464 
     | 
    
         
            +
              Doing.logger.output_results
         
     | 
| 
      
 2465 
     | 
    
         
            +
            end
         
     | 
| 
      
 2466 
     | 
    
         
            +
             
     | 
| 
      
 2467 
     | 
    
         
            +
            around do |global, command, options, arguments, code|
         
     | 
| 
      
 2468 
     | 
    
         
            +
              # Doing.logger.debug('Pager:', "Global: #{global[:pager]}, Config: #{settings['paginate']}, Pager: #{Doing::Pager.paginate}")
         
     | 
| 
      
 2469 
     | 
    
         
            +
              Doing.logger.adjust_verbosity(global)
         
     | 
| 
       1964 
2470 
     | 
    
         | 
| 
       1965 
2471 
     | 
    
         
             
              if global[:stdout]
         
     | 
| 
       1966 
     | 
    
         
            -
                $stdout 
     | 
| 
      
 2472 
     | 
    
         
            +
                Doing.logger.logdev = $stdout
         
     | 
| 
      
 2473 
     | 
    
         
            +
              end
         
     | 
| 
      
 2474 
     | 
    
         
            +
             
     | 
| 
      
 2475 
     | 
    
         
            +
              wwid.default_option = global[:default]
         
     | 
| 
      
 2476 
     | 
    
         
            +
             
     | 
| 
      
 2477 
     | 
    
         
            +
              if global[:config_file] && global[:config_file] != config.config_file
         
     | 
| 
      
 2478 
     | 
    
         
            +
                Doing.logger.warn(format('%sWARNING:%s %sThe use of --config_file is deprecated, please set the environment variable DOING_CONFIG instead.', colors.flamingo, colors.default, colors.boldred))
         
     | 
| 
      
 2479 
     | 
    
         
            +
                Doing.logger.warn(format('%sTo set it just for the current command, use: %sDOING_CONFIG=/path/to/doingrc doing [command]%s',  colors.red, colors.boldwhite, colors.default))
         
     | 
| 
      
 2480 
     | 
    
         
            +
             
     | 
| 
      
 2481 
     | 
    
         
            +
                cf = File.expand_path(global[:config_file])
         
     | 
| 
      
 2482 
     | 
    
         
            +
                raise MissingConfigFile, "Config file not found (#{global[:config_file]})" unless File.exist?(cf)
         
     | 
| 
      
 2483 
     | 
    
         
            +
             
     | 
| 
      
 2484 
     | 
    
         
            +
                config.config_file = cf
         
     | 
| 
      
 2485 
     | 
    
         
            +
                settings = config.configure({ ignore_local: true })
         
     | 
| 
      
 2486 
     | 
    
         
            +
              end
         
     | 
| 
      
 2487 
     | 
    
         
            +
             
     | 
| 
      
 2488 
     | 
    
         
            +
              if global[:doing_file]
         
     | 
| 
      
 2489 
     | 
    
         
            +
                wwid.init_doing_file(global[:doing_file])
         
     | 
| 
       1967 
2490 
     | 
    
         
             
              else
         
     | 
| 
       1968 
     | 
    
         
            -
                 
     | 
| 
      
 2491 
     | 
    
         
            +
                wwid.init_doing_file
         
     | 
| 
       1969 
2492 
     | 
    
         
             
              end
         
     | 
| 
       1970 
     | 
    
         
            -
            end
         
     | 
| 
       1971 
2493 
     | 
    
         | 
| 
       1972 
     | 
    
         
            -
             
     | 
| 
       1973 
     | 
    
         
            -
             
     | 
| 
       1974 
     | 
    
         
            -
               
     | 
| 
      
 2494 
     | 
    
         
            +
              wwid.auto_tag = !global[:noauto]
         
     | 
| 
      
 2495 
     | 
    
         
            +
             
     | 
| 
      
 2496 
     | 
    
         
            +
              settings[:include_notes] = false unless global[:notes]
         
     | 
| 
      
 2497 
     | 
    
         
            +
             
     | 
| 
      
 2498 
     | 
    
         
            +
              global[:wwid] = wwid
         
     | 
| 
      
 2499 
     | 
    
         
            +
             
     | 
| 
      
 2500 
     | 
    
         
            +
              code.call
         
     | 
| 
       1975 
2501 
     | 
    
         
             
            end
         
     | 
| 
       1976 
2502 
     | 
    
         | 
| 
       1977 
2503 
     | 
    
         
             
            exit run(ARGV)
         
     |