doing 1.0.93 → 2.0.6.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 +616 -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 +1055 -494
 - data/doing.gemspec +34 -0
 - data/doing.rdoc +1839 -0
 - data/example_plugin.rb +209 -0
 - data/generate_completions.sh +5 -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 +203 -0
 - data/lib/completion/doing.bash +449 -0
 - data/lib/completion/doing.fish +329 -0
 - data/lib/doing/array.rb +8 -0
 - data/lib/doing/cli_status.rb +70 -0
 - data/lib/doing/colors.rb +136 -0
 - data/lib/doing/configuration.rb +312 -0
 - data/lib/doing/errors.rb +109 -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 +344 -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 +348 -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 +1868 -2349
 - data/lib/doing/wwidfile.rb +117 -0
 - data/lib/doing.rb +43 -3
 - data/lib/examples/commands/autotag.rb +63 -0
 - data/lib/examples/commands/wiki.rb +81 -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 +211 -0
 - data/scripts/generate_fish_completions.rb +204 -0
 - data/scripts/generate_zsh_completions.rb +168 -0
 - metadata +82 -7
 - data/lib/doing/helpers.rb +0 -191
 - data/lib/doing/markdown_export.rb +0 -16
 
| 
         @@ -0,0 +1,117 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Doing
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Items < Array
         
     | 
| 
      
 5 
     | 
    
         
            +
                def from(path)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  return [] unless path
         
     | 
| 
      
 7 
     | 
    
         
            +
                  path = File.expand_path(path)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  if File.exist?(path) && File.file?(path) && File.stat(path).size.positive?
         
     | 
| 
      
 10 
     | 
    
         
            +
                    input = IO.read(File.expand_path(input))
         
     | 
| 
      
 11 
     | 
    
         
            +
                    input = input.force_encoding('utf-8') if input.respond_to? :force_encoding
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  section = 'Uncategorized'
         
     | 
| 
      
 15 
     | 
    
         
            +
                  lines = input.split(/[\n\r]/)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  current = 0
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  lines.each do |line|
         
     | 
| 
      
 19 
     | 
    
         
            +
                    next if line =~ /^\s*$/
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    if line =~ /^(\S[\S ]+):\s*(@\S+\s*)*$/
         
     | 
| 
      
 22 
     | 
    
         
            +
                      section = Regexp.last_match(1)
         
     | 
| 
      
 23 
     | 
    
         
            +
                      @sections << { original: line, title: section }
         
     | 
| 
      
 24 
     | 
    
         
            +
                      current = 0
         
     | 
| 
      
 25 
     | 
    
         
            +
                    elsif line =~ /^\s*- (\d{4}-\d\d-\d\d \d\d:\d\d) \| (.*)/
         
     | 
| 
      
 26 
     | 
    
         
            +
                      date = Regexp.last_match(1).strip
         
     | 
| 
      
 27 
     | 
    
         
            +
                      title = Regexp.last_match(2).strip
         
     | 
| 
      
 28 
     | 
    
         
            +
                      item = Item.new(date, title, section)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      @items.push(item)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      current += 1
         
     | 
| 
      
 31 
     | 
    
         
            +
                    elsif current.zero?
         
     | 
| 
      
 32 
     | 
    
         
            +
                      # if content[section][:items].length - 1 == current
         
     | 
| 
      
 33 
     | 
    
         
            +
                      @other_content_top.push(line)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    elsif line =~ /^\S/
         
     | 
| 
      
 35 
     | 
    
         
            +
                      @other_content_bottom.push(line)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    else
         
     | 
| 
      
 37 
     | 
    
         
            +
                      prev_item = @items[current - 1]
         
     | 
| 
      
 38 
     | 
    
         
            +
                      prev_item.note = Note.new unless prev_item.note
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                      prev_item.note.add(line)
         
     | 
| 
      
 41 
     | 
    
         
            +
                      # end
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  Hooks.trigger :post_read, self
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def section_titles
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @sections.map { |s| s[:title] }
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                ##
         
     | 
| 
      
 52 
     | 
    
         
            +
                ## @brief      Adds a section.
         
     | 
| 
      
 53 
     | 
    
         
            +
                ##
         
     | 
| 
      
 54 
     | 
    
         
            +
                ## @param      title  (String) The new section title
         
     | 
| 
      
 55 
     | 
    
         
            +
                ##
         
     | 
| 
      
 56 
     | 
    
         
            +
                def add_section(title)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  if section_titles.include?(title.cap_first)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    Doing.logger.debug('Skipped': 'Section already exists')
         
     | 
| 
      
 59 
     | 
    
         
            +
                    return
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                  @sections << { original: "#{title}:", title: title }
         
     | 
| 
      
 63 
     | 
    
         
            +
                  Doing.logger.info('Added section:', %("#{title.cap_first}"))
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                ##
         
     | 
| 
      
 67 
     | 
    
         
            +
                ## @brief      Attempt to match a string with an existing section
         
     | 
| 
      
 68 
     | 
    
         
            +
                ##
         
     | 
| 
      
 69 
     | 
    
         
            +
                ## @param      frag     (String) The user-provided string
         
     | 
| 
      
 70 
     | 
    
         
            +
                ## @param      guessed  (Boolean) already guessed and failed
         
     | 
| 
      
 71 
     | 
    
         
            +
                ##
         
     | 
| 
      
 72 
     | 
    
         
            +
                def guess_section(frag, guessed: false, suggest: false)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  return 'All' if frag =~ /^all$/i
         
     | 
| 
      
 74 
     | 
    
         
            +
                  frag ||= wwid.config['current_section']
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                  @sections.each { |sect| return sect[:title].cap_first if frag.downcase == sect[:title].downcase }
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  section = false
         
     | 
| 
      
 79 
     | 
    
         
            +
                  re = frag.split('').join('.*?')
         
     | 
| 
      
 80 
     | 
    
         
            +
                  sections.each do |sect|
         
     | 
| 
      
 81 
     | 
    
         
            +
                    next unless sect =~ /#{re}/i
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                    Doing.logger.debug('Section match:', %(Assuming "#{sect}" from "#{frag}"))
         
     | 
| 
      
 84 
     | 
    
         
            +
                    section = sect
         
     | 
| 
      
 85 
     | 
    
         
            +
                    break
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  return section if suggest
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  unless section || guessed
         
     | 
| 
      
 91 
     | 
    
         
            +
                    alt = WWID.guess_view(frag, guessed: true, suggest: true)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    if alt
         
     | 
| 
      
 93 
     | 
    
         
            +
                      meant_view = WWID.yn("Did you mean `doing view #{alt}`?", default_response: 'n')
         
     | 
| 
      
 94 
     | 
    
         
            +
                      raise Errors::InvalidSection, "Run again with `doing view #{alt}`" if meant_view
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                    res = WWID.yn("Section #{frag} not found, create it", default_response: 'n')
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                    if res
         
     | 
| 
      
 100 
     | 
    
         
            +
                      add_section(frag.cap_first)
         
     | 
| 
      
 101 
     | 
    
         
            +
                      WWID.write(@doing_file)
         
     | 
| 
      
 102 
     | 
    
         
            +
                      return frag.cap_first
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                    raise Errors::InvalidSection, "Unknown section: #{frag}"
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  section ? section.cap_first : guessed
         
     | 
| 
      
 108 
     | 
    
         
            +
                end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                def section_items(section)
         
     | 
| 
      
 111 
     | 
    
         
            +
                  section = guess_section(section)
         
     | 
| 
      
 112 
     | 
    
         
            +
                  return @items if section =~ /all/i
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                  @items.filter { |i| i.section == section }
         
     | 
| 
      
 115 
     | 
    
         
            +
                end
         
     | 
| 
      
 116 
     | 
    
         
            +
              end
         
     | 
| 
      
 117 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/doing.rb
    CHANGED
    
    | 
         @@ -1,4 +1,6 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
             
     | 
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'doing/version'
         
     | 
| 
       2 
4 
     | 
    
         
             
            require 'time'
         
     | 
| 
       3 
5 
     | 
    
         
             
            require 'date'
         
     | 
| 
       4 
6 
     | 
    
         
             
            require 'yaml'
         
     | 
| 
         @@ -8,7 +10,45 @@ require 'tempfile' 
     | 
|
| 
       8 
10 
     | 
    
         
             
            require 'chronic'
         
     | 
| 
       9 
11 
     | 
    
         
             
            require 'haml'
         
     | 
| 
       10 
12 
     | 
    
         
             
            require 'json'
         
     | 
| 
       11 
     | 
    
         
            -
            require ' 
     | 
| 
       12 
     | 
    
         
            -
            require ' 
     | 
| 
      
 13 
     | 
    
         
            +
            require 'logger'
         
     | 
| 
      
 14 
     | 
    
         
            +
            require 'safe_yaml/load'
         
     | 
| 
      
 15 
     | 
    
         
            +
            require 'doing/hash'
         
     | 
| 
      
 16 
     | 
    
         
            +
            require 'doing/colors'
         
     | 
| 
      
 17 
     | 
    
         
            +
            require 'doing/string'
         
     | 
| 
      
 18 
     | 
    
         
            +
            require 'doing/time'
         
     | 
| 
      
 19 
     | 
    
         
            +
            require 'doing/array'
         
     | 
| 
      
 20 
     | 
    
         
            +
            require 'doing/symbol'
         
     | 
| 
      
 21 
     | 
    
         
            +
            require 'doing/util'
         
     | 
| 
      
 22 
     | 
    
         
            +
            require 'doing/configuration'
         
     | 
| 
      
 23 
     | 
    
         
            +
            require 'doing/item'
         
     | 
| 
      
 24 
     | 
    
         
            +
            require 'doing/note'
         
     | 
| 
      
 25 
     | 
    
         
            +
            require 'doing/wwidfile'
         
     | 
| 
       13 
26 
     | 
    
         
             
            require 'doing/wwid'
         
     | 
| 
      
 27 
     | 
    
         
            +
            require 'doing/log_adapter'
         
     | 
| 
      
 28 
     | 
    
         
            +
            require 'doing/errors'
         
     | 
| 
      
 29 
     | 
    
         
            +
            require 'doing/hooks'
         
     | 
| 
      
 30 
     | 
    
         
            +
            require 'doing/plugin_manager'
         
     | 
| 
      
 31 
     | 
    
         
            +
            require 'doing/pager'
         
     | 
| 
       14 
32 
     | 
    
         
             
            # require 'doing/markdown_document_listener'
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            # Main doing module
         
     | 
| 
      
 35 
     | 
    
         
            +
            module Doing
         
     | 
| 
      
 36 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 37 
     | 
    
         
            +
                #
         
     | 
| 
      
 38 
     | 
    
         
            +
                # @brief      Fetch the logger
         
     | 
| 
      
 39 
     | 
    
         
            +
                #
         
     | 
| 
      
 40 
     | 
    
         
            +
                # @return     the LogAdapter instance.
         
     | 
| 
      
 41 
     | 
    
         
            +
                #
         
     | 
| 
      
 42 
     | 
    
         
            +
                def logger
         
     | 
| 
      
 43 
     | 
    
         
            +
                  @logger ||= LogAdapter.new((ENV['DOING_LOG_LEVEL'] || :info).to_sym)
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def config
         
     | 
| 
      
 47 
     | 
    
         
            +
                  @config ||= Configuration.new
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                def config_with(file, options = {})
         
     | 
| 
      
 51 
     | 
    
         
            +
                  @config = Configuration.new(file, options: options)
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,63 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # Example command that calls an existing command (tag) with
         
     | 
| 
      
 4 
     | 
    
         
            +
            # preset options
         
     | 
| 
      
 5 
     | 
    
         
            +
            desc 'Autotag last entry or filtered entries'
         
     | 
| 
      
 6 
     | 
    
         
            +
            command :autotag do |c|
         
     | 
| 
      
 7 
     | 
    
         
            +
              # Preserve some switches and flags. Values will be passed
         
     | 
| 
      
 8 
     | 
    
         
            +
              # to tag command.
         
     | 
| 
      
 9 
     | 
    
         
            +
              c.desc 'Section'
         
     | 
| 
      
 10 
     | 
    
         
            +
              c.arg_name 'SECTION_NAME'
         
     | 
| 
      
 11 
     | 
    
         
            +
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              c.desc 'How many recent entries to autotag (0 for all)'
         
     | 
| 
      
 14 
     | 
    
         
            +
              c.arg_name 'COUNT'
         
     | 
| 
      
 15 
     | 
    
         
            +
              c.flag %i[c count], default_value: 1, must_match: /^\d+$/, type: Integer
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              c.desc 'Don\'t ask permission to autotag all entries when count is 0'
         
     | 
| 
      
 18 
     | 
    
         
            +
              c.switch %i[force], negatable: false, default_value: false
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              c.desc 'Autotag last entry (or entries) not marked @done'
         
     | 
| 
      
 21 
     | 
    
         
            +
              c.switch %i[u unfinished], negatable: false, default_value: false
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              c.desc 'Autotag the last X entries containing TAG.
         
     | 
| 
      
 24 
     | 
    
         
            +
              Separate multiple tags with comma (--tag=tag1,tag2), combine with --bool'
         
     | 
| 
      
 25 
     | 
    
         
            +
              c.arg_name 'TAG'
         
     | 
| 
      
 26 
     | 
    
         
            +
              c.flag [:tag]
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              c.desc 'Autotag entries matching search filter,
         
     | 
| 
      
 29 
     | 
    
         
            +
              surround with slashes for regex (e.g. "/query.*/"),
         
     | 
| 
      
 30 
     | 
    
         
            +
              start with single quote for exact match ("\'query")'
         
     | 
| 
      
 31 
     | 
    
         
            +
              c.arg_name 'QUERY'
         
     | 
| 
      
 32 
     | 
    
         
            +
              c.flag [:search]
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              c.desc 'Boolean (AND|OR|NOT) with which to combine multiple tag filters'
         
     | 
| 
      
 35 
     | 
    
         
            +
              c.arg_name 'BOOLEAN'
         
     | 
| 
      
 36 
     | 
    
         
            +
              c.flag [:bool], must_match: REGEX_BOOL, default_value: 'AND'
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              c.desc 'Select item(s) to tag from a menu of matching entries'
         
     | 
| 
      
 39 
     | 
    
         
            +
              c.switch %i[i interactive], negatable: false, default_value: false
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
              c.action do |global, options, _args|
         
     | 
| 
      
 42 
     | 
    
         
            +
                # Force some switches and flags. We're using the tag
         
     | 
| 
      
 43 
     | 
    
         
            +
                # command with settings that would invoke autotagging.
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                # Force enable autotag
         
     | 
| 
      
 46 
     | 
    
         
            +
                options[:a] = true
         
     | 
| 
      
 47 
     | 
    
         
            +
                options[:autotag] = true
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                # No need for date values
         
     | 
| 
      
 50 
     | 
    
         
            +
                options[:d] = false
         
     | 
| 
      
 51 
     | 
    
         
            +
                options[:date] = false
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                # Don't remove any tags
         
     | 
| 
      
 54 
     | 
    
         
            +
                options[:rename] = nil
         
     | 
| 
      
 55 
     | 
    
         
            +
                options[:regex] = false
         
     | 
| 
      
 56 
     | 
    
         
            +
                options[:r] = false
         
     | 
| 
      
 57 
     | 
    
         
            +
                options[:remove] = false
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                cmd = commands[:tag]
         
     | 
| 
      
 60 
     | 
    
         
            +
                action = cmd.send(:get_action, nil)
         
     | 
| 
      
 61 
     | 
    
         
            +
                action.call(global, options, [])
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,81 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            desc 'Output a tag wiki'
         
     | 
| 
      
 2 
     | 
    
         
            +
            command :wiki do |c|
         
     | 
| 
      
 3 
     | 
    
         
            +
              c.desc 'Section to rotate'
         
     | 
| 
      
 4 
     | 
    
         
            +
              c.arg_name 'SECTION_NAME'
         
     | 
| 
      
 5 
     | 
    
         
            +
              c.flag %i[s section], default_value: 'All'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              c.desc 'Tag filter, combine multiple tags with a comma, use with --bool'
         
     | 
| 
      
 8 
     | 
    
         
            +
              c.arg_name 'TAG'
         
     | 
| 
      
 9 
     | 
    
         
            +
              c.flag [:tag]
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              c.desc 'Tag boolean (AND,OR,NOT)'
         
     | 
| 
      
 12 
     | 
    
         
            +
              c.arg_name 'BOOLEAN'
         
     | 
| 
      
 13 
     | 
    
         
            +
              c.flag %i[b bool], must_match: REGEX_BOOL, default_value: 'OR'
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              c.desc 'Include entries older than date'
         
     | 
| 
      
 16 
     | 
    
         
            +
              c.arg_name 'DATE_STRING'
         
     | 
| 
      
 17 
     | 
    
         
            +
              c.flag [:before]
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              c.desc 'Include entries newer than date'
         
     | 
| 
      
 20 
     | 
    
         
            +
              c.arg_name 'DATE_STRING'
         
     | 
| 
      
 21 
     | 
    
         
            +
              c.flag [:after]
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              c.desc 'Search filter, surround with slashes for regex (/query/), start with single quote for exact match ("\'query")'
         
     | 
| 
      
 24 
     | 
    
         
            +
              c.arg_name 'QUERY'
         
     | 
| 
      
 25 
     | 
    
         
            +
              c.flag [:search]
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              c.desc %(
         
     | 
| 
      
 28 
     | 
    
         
            +
                Date range to include, or a single day to filter date on.
         
     | 
| 
      
 29 
     | 
    
         
            +
                Date range argument should be quoted. Date specifications can be natural language.
         
     | 
| 
      
 30 
     | 
    
         
            +
                To specify a range, use "to" or "through": `doing show --from "monday to friday"`
         
     | 
| 
      
 31 
     | 
    
         
            +
              )
         
     | 
| 
      
 32 
     | 
    
         
            +
              c.arg_name 'DATE_OR_RANGE'
         
     | 
| 
      
 33 
     | 
    
         
            +
              c.flag %i[f from]
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              c.desc 'Only show items with recorded time intervals'
         
     | 
| 
      
 36 
     | 
    
         
            +
              c.switch [:only_timed], default_value: false, negatable: false
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              c.action do |global, options, args|
         
     | 
| 
      
 39 
     | 
    
         
            +
                wwid = global[:wwid]
         
     | 
| 
      
 40 
     | 
    
         
            +
                tags = wwid.tag_groups([], opt: options)
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                wiki = Doing::Plugins.plugins.dig(:export, 'wiki', :class)
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                tags.each do |tag, items|
         
     | 
| 
      
 45 
     | 
    
         
            +
                  export_options = { page_title: tag, is_single: false, options: options }
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  raise RuntimeError, 'Missing plugin "wiki"' unless wiki
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  out = wiki.render(wwid, items, variables: export_options)
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                  if out
         
     | 
| 
      
 52 
     | 
    
         
            +
                    FileUtils.mkdir_p('doing_wiki')
         
     | 
| 
      
 53 
     | 
    
         
            +
                    File.open(File.join('doing_wiki', tag + '.html'), 'w') do |f|
         
     | 
| 
      
 54 
     | 
    
         
            +
                      f.puts out
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                template = if wwid.config['export_templates']['wiki_index'] && File.exist?(File.expand_path(wwid.config['export_templates']['wiki_index']))
         
     | 
| 
      
 60 
     | 
    
         
            +
                             IO.read(File.expand_path(wwid.config['export_templates']['wiki_index']))
         
     | 
| 
      
 61 
     | 
    
         
            +
                           else
         
     | 
| 
      
 62 
     | 
    
         
            +
                             wiki.template('wiki_index')
         
     | 
| 
      
 63 
     | 
    
         
            +
                           end
         
     | 
| 
      
 64 
     | 
    
         
            +
                style = if wwid.config['export_templates']['wiki_css'] && File.exist?(File.expand_path(wwid.config['export_templates']['wiki_css']))
         
     | 
| 
      
 65 
     | 
    
         
            +
                          IO.read(File.expand_path(wwid.config['export_templates']['wiki_css']))
         
     | 
| 
      
 66 
     | 
    
         
            +
                        else
         
     | 
| 
      
 67 
     | 
    
         
            +
                          wiki.template('wiki_css')
         
     | 
| 
      
 68 
     | 
    
         
            +
                        end
         
     | 
| 
      
 69 
     | 
    
         
            +
                tags_out = tags.map { |t| {url: "#{t}.html"} }
         
     | 
| 
      
 70 
     | 
    
         
            +
                engine = Haml::Engine.new(template)
         
     | 
| 
      
 71 
     | 
    
         
            +
                index_out = engine.render(Object.new,
         
     | 
| 
      
 72 
     | 
    
         
            +
                                   { :@tags => tags.each_with_object([]) { |(tag, items), arr| arr << { name: tag, count: items.count } }, :@page_title => "Tags wiki", :@style => style })
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                if index_out
         
     | 
| 
      
 75 
     | 
    
         
            +
                  File.open(File.join('doing_wiki', 'index.html'), 'w') do |f|
         
     | 
| 
      
 76 
     | 
    
         
            +
                    f.puts index_out
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
                  Doing.logger.warn("Wiki written to doing_wiki directory")
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,22 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Doing
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Hooks.register :post_config do |wwid|
         
     | 
| 
      
 5 
     | 
    
         
            +
              #   wwid.config['twizzle'] = 'Fo shizzle'
         
     | 
| 
      
 6 
     | 
    
         
            +
              #   wwid.write_config(File.expand_path('~/Desktop/wwidconfig.yml'))
         
     | 
| 
      
 7 
     | 
    
         
            +
              # end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              # Hooks.register :post_read, priority: 10 do |wwid|
         
     | 
| 
      
 10 
     | 
    
         
            +
              #   Doing.logger.warn('Hook 1:', 'triggered priority 10')
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   Doing.logger.warn('Hook 2:', wwid.config['twizzle'])
         
     | 
| 
      
 12 
     | 
    
         
            +
              # end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              # Hooks.register :post_read, priority: 100 do |wwid|
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   Doing.logger.warn('Hook 2:', 'triggered priority 100')
         
     | 
| 
      
 16 
     | 
    
         
            +
              # end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              Hooks.register :post_write do |filename|
         
     | 
| 
      
 19 
     | 
    
         
            +
                res = `/bin/bash /Users/ttscoff/scripts/after_doing.sh`.strip
         
     | 
| 
      
 20 
     | 
    
         
            +
                Doing.logger.debug('Hooks:', res) unless res =~ /^\.\.\.$/
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,202 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # title: Export plugin example
         
     | 
| 
      
 4 
     | 
    
         
            +
            # description: Speak the most recent entry (macOS)
         
     | 
| 
      
 5 
     | 
    
         
            +
            # author: Brett Terpstra
         
     | 
| 
      
 6 
     | 
    
         
            +
            # url: https://brettterpstra.com
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            # Example
         
     | 
| 
      
 9 
     | 
    
         
            +
            #
         
     | 
| 
      
 10 
     | 
    
         
            +
            # doing show -o sayit
         
     | 
| 
      
 11 
     | 
    
         
            +
            #
         
     | 
| 
      
 12 
     | 
    
         
            +
            # ## Configuration
         
     | 
| 
      
 13 
     | 
    
         
            +
            #
         
     | 
| 
      
 14 
     | 
    
         
            +
            # Change what the plugin says by generating a template with
         
     | 
| 
      
 15 
     | 
    
         
            +
            # `doing template --type say`, saving it to a file, and
         
     | 
| 
      
 16 
     | 
    
         
            +
            # putting the path to that file in `export_templates->say` in
         
     | 
| 
      
 17 
     | 
    
         
            +
            # .doingrc.
         
     | 
| 
      
 18 
     | 
    
         
            +
            #
         
     | 
| 
      
 19 
     | 
    
         
            +
            # export_templates:
         
     | 
| 
      
 20 
     | 
    
         
            +
            #   say: /path/to/template.txt
         
     | 
| 
      
 21 
     | 
    
         
            +
            #
         
     | 
| 
      
 22 
     | 
    
         
            +
            # Use a different voice by adding a `say_voice` key to your
         
     | 
| 
      
 23 
     | 
    
         
            +
            # .doingrc. Use `say -v ?` to see available voices.
         
     | 
| 
      
 24 
     | 
    
         
            +
            #
         
     | 
| 
      
 25 
     | 
    
         
            +
            # say_voice: Zarvox
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
            module Doing
         
     | 
| 
      
 28 
     | 
    
         
            +
              ##
         
     | 
| 
      
 29 
     | 
    
         
            +
              ## @brief      Plugin class
         
     | 
| 
      
 30 
     | 
    
         
            +
              ##
         
     | 
| 
      
 31 
     | 
    
         
            +
              class SayExport
         
     | 
| 
      
 32 
     | 
    
         
            +
                include Doing::Util
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                #-------------------------------------------------------
         
     | 
| 
      
 35 
     | 
    
         
            +
                ## Plugin Settings. A plugin must have a self.settings
         
     | 
| 
      
 36 
     | 
    
         
            +
                ## method that returns a hash with plugin settings.
         
     | 
| 
      
 37 
     | 
    
         
            +
                ##
         
     | 
| 
      
 38 
     | 
    
         
            +
                ## trigger:   (required) Regular expression to match
         
     | 
| 
      
 39 
     | 
    
         
            +
                ## FORMAT when used with `--output FORMAT`. Registered
         
     | 
| 
      
 40 
     | 
    
         
            +
                ## name of plugin must be able to match the trigger, but
         
     | 
| 
      
 41 
     | 
    
         
            +
                ## alternatives can be included
         
     | 
| 
      
 42 
     | 
    
         
            +
                ##
         
     | 
| 
      
 43 
     | 
    
         
            +
                ## templates: (optional) Array of templates this plugin
         
     | 
| 
      
 44 
     | 
    
         
            +
                ## can export (plugin must have :template method)
         
     | 
| 
      
 45 
     | 
    
         
            +
                ##
         
     | 
| 
      
 46 
     | 
    
         
            +
                ##   Each template is a hash containing:
         
     | 
| 
      
 47 
     | 
    
         
            +
                ##               - name: display name for template
         
     | 
| 
      
 48 
     | 
    
         
            +
                ##               - trigger: regular expression for
         
     | 
| 
      
 49 
     | 
    
         
            +
                ##                 `template --type FORMAT`
         
     | 
| 
      
 50 
     | 
    
         
            +
                ##
         
     | 
| 
      
 51 
     | 
    
         
            +
                ##   If a template is included, a config key will
         
     | 
| 
      
 52 
     | 
    
         
            +
                ##   automatically be added for the user to override
         
     | 
| 
      
 53 
     | 
    
         
            +
                ##   The config key will be available at:
         
     | 
| 
      
 54 
     | 
    
         
            +
                ##
         
     | 
| 
      
 55 
     | 
    
         
            +
                ##       wwid.config['export_templates'][PLUGIN_NAME]
         
     | 
| 
      
 56 
     | 
    
         
            +
                ##
         
     | 
| 
      
 57 
     | 
    
         
            +
                ## config:    (optional) A Hash which will be
         
     | 
| 
      
 58 
     | 
    
         
            +
                ## added to the main configuration in the plugins section.
         
     | 
| 
      
 59 
     | 
    
         
            +
                ## Options defined here are included when config file is
         
     | 
| 
      
 60 
     | 
    
         
            +
                ## created or updated with `config --update`. Use this to
         
     | 
| 
      
 61 
     | 
    
         
            +
                ## add new configuration keys, not to override existing
         
     | 
| 
      
 62 
     | 
    
         
            +
                ## ones.
         
     | 
| 
      
 63 
     | 
    
         
            +
                ##
         
     | 
| 
      
 64 
     | 
    
         
            +
                ##   The configuration keys will be available at:
         
     | 
| 
      
 65 
     | 
    
         
            +
                ##
         
     | 
| 
      
 66 
     | 
    
         
            +
                ##      wwid.config['plugins'][PLUGIN_NAME][KEY]
         
     | 
| 
      
 67 
     | 
    
         
            +
                ##
         
     | 
| 
      
 68 
     | 
    
         
            +
                ## @brief      Method to return plugin settings (required)
         
     | 
| 
      
 69 
     | 
    
         
            +
                ##
         
     | 
| 
      
 70 
     | 
    
         
            +
                ## @return     Hash of settings for this plugin
         
     | 
| 
      
 71 
     | 
    
         
            +
                ##
         
     | 
| 
      
 72 
     | 
    
         
            +
                def self.settings
         
     | 
| 
      
 73 
     | 
    
         
            +
                  {
         
     | 
| 
      
 74 
     | 
    
         
            +
                    trigger: 'say(?:it)?',
         
     | 
| 
      
 75 
     | 
    
         
            +
                    templates: [
         
     | 
| 
      
 76 
     | 
    
         
            +
                      { name: 'say', trigger: 'say(?:it)?' }
         
     | 
| 
      
 77 
     | 
    
         
            +
                    ],
         
     | 
| 
      
 78 
     | 
    
         
            +
                    config: {
         
     | 
| 
      
 79 
     | 
    
         
            +
                      'say_voice' => 'Fiona'
         
     | 
| 
      
 80 
     | 
    
         
            +
                    }
         
     | 
| 
      
 81 
     | 
    
         
            +
                  }
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                #-------------------------------------------------------
         
     | 
| 
      
 86 
     | 
    
         
            +
                ## Output a template. Only required if template(s) are
         
     | 
| 
      
 87 
     | 
    
         
            +
                ## included in settings. The method should return a
         
     | 
| 
      
 88 
     | 
    
         
            +
                ## string (not output it to the STDOUT).
         
     | 
| 
      
 89 
     | 
    
         
            +
                ##
         
     | 
| 
      
 90 
     | 
    
         
            +
                ## @brief      Method to return template (optional)
         
     | 
| 
      
 91 
     | 
    
         
            +
                ##
         
     | 
| 
      
 92 
     | 
    
         
            +
                ## @param      trigger  The trigger passed to the
         
     | 
| 
      
 93 
     | 
    
         
            +
                ##                      template function. When this
         
     | 
| 
      
 94 
     | 
    
         
            +
                ##                      method defines multiple
         
     | 
| 
      
 95 
     | 
    
         
            +
                ##                      templates, the trigger can be
         
     | 
| 
      
 96 
     | 
    
         
            +
                ##                      used to determine which one is
         
     | 
| 
      
 97 
     | 
    
         
            +
                ##                      output.
         
     | 
| 
      
 98 
     | 
    
         
            +
                ##
         
     | 
| 
      
 99 
     | 
    
         
            +
                ## @return     (String) template contents
         
     | 
| 
      
 100 
     | 
    
         
            +
                ##
         
     | 
| 
      
 101 
     | 
    
         
            +
                def self.template(trigger)
         
     | 
| 
      
 102 
     | 
    
         
            +
                  return unless trigger =~ /^say(it)?$/
         
     | 
| 
      
 103 
     | 
    
         
            +
                  'On %date, you were %title, recorded in section %section%took'
         
     | 
| 
      
 104 
     | 
    
         
            +
                end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                ##
         
     | 
| 
      
 108 
     | 
    
         
            +
                ## @brief      Render data received from an output
         
     | 
| 
      
 109 
     | 
    
         
            +
                ##             command
         
     | 
| 
      
 110 
     | 
    
         
            +
                ##
         
     | 
| 
      
 111 
     | 
    
         
            +
                ## @param      wwid       The wwid object with config
         
     | 
| 
      
 112 
     | 
    
         
            +
                ##                        and public methods
         
     | 
| 
      
 113 
     | 
    
         
            +
                ## @param      items      An array of items to be output
         
     | 
| 
      
 114 
     | 
    
         
            +
                ##                        { <Date>date, <String>title,
         
     | 
| 
      
 115 
     | 
    
         
            +
                ##                        <String>section, <Array>note }
         
     | 
| 
      
 116 
     | 
    
         
            +
                ## @param      variables  Additional variables including
         
     | 
| 
      
 117 
     | 
    
         
            +
                ##                        flags passed to command
         
     | 
| 
      
 118 
     | 
    
         
            +
                ##                        (variables[:options])
         
     | 
| 
      
 119 
     | 
    
         
            +
                ##
         
     | 
| 
      
 120 
     | 
    
         
            +
                ## @return     (String) Rendered output
         
     | 
| 
      
 121 
     | 
    
         
            +
                ##
         
     | 
| 
      
 122 
     | 
    
         
            +
                def self.render(wwid, items, variables: {})
         
     | 
| 
      
 123 
     | 
    
         
            +
                  return if items.nil? || items.empty?
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                  # the :options key includes the flags passed to the
         
     | 
| 
      
 126 
     | 
    
         
            +
                  # command that called the plugin use `puts
         
     | 
| 
      
 127 
     | 
    
         
            +
                  # variables.inspect` to see properties and methods
         
     | 
| 
      
 128 
     | 
    
         
            +
                  # when run
         
     | 
| 
      
 129 
     | 
    
         
            +
                  opt = variables[:options]
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                  # This plugin just grabs the last item in the `items`
         
     | 
| 
      
 132 
     | 
    
         
            +
                  # list (which could be the oldest or newest, depending
         
     | 
| 
      
 133 
     | 
    
         
            +
                  # on the sort order of the command that called the
         
     | 
| 
      
 134 
     | 
    
         
            +
                  # plugin). Most of the time you'll want to use :each
         
     | 
| 
      
 135 
     | 
    
         
            +
                  # or :map to generate output.
         
     | 
| 
      
 136 
     | 
    
         
            +
                  i = items[-1]
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                  # Format the item. Items are a hash with 3 keys: date,
         
     | 
| 
      
 139 
     | 
    
         
            +
                  # title, and section (parent section) Start time is in
         
     | 
| 
      
 140 
     | 
    
         
            +
                  # item.date. The wwid object has some methods for
         
     | 
| 
      
 141 
     | 
    
         
            +
                  # calculation and formatting, including
         
     | 
| 
      
 142 
     | 
    
         
            +
                  # wwid.item.end_date to convert the @done
         
     | 
| 
      
 143 
     | 
    
         
            +
                  # timestamp to an end date.
         
     | 
| 
      
 144 
     | 
    
         
            +
                  if opt[:times]
         
     | 
| 
      
 145 
     | 
    
         
            +
                    interval = i.interval
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                    if interval
         
     | 
| 
      
 148 
     | 
    
         
            +
                      took = '. You finished on '
         
     | 
| 
      
 149 
     | 
    
         
            +
                      finished_at = i.end_date
         
     | 
| 
      
 150 
     | 
    
         
            +
                      took += finished_at.strftime('%A %B %e at %I:%M%p')
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                      d, h, m = wwid.format_time(interval)
         
     | 
| 
      
 153 
     | 
    
         
            +
                      took += ' and it took'
         
     | 
| 
      
 154 
     | 
    
         
            +
                      took += " #{d.to_i} days" if d.to_i.positive?
         
     | 
| 
      
 155 
     | 
    
         
            +
                      took += " #{h.to_i} hours" if h.to_i.positive?
         
     | 
| 
      
 156 
     | 
    
         
            +
                      took += " #{m.to_i} minutes" if m.to_i.positive?
         
     | 
| 
      
 157 
     | 
    
         
            +
                    end
         
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                  date = i.date.strftime('%A %B %e at %I:%M%p')
         
     | 
| 
      
 161 
     | 
    
         
            +
                  title = i.title.gsub(/ @done\(.*?\)/, '').gsub(/@/, 'hashtag ')
         
     | 
| 
      
 162 
     | 
    
         
            +
                  tpl = template('say')
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                  if wwid.config['export_templates'].key?('say')
         
     | 
| 
      
 165 
     | 
    
         
            +
                    cfg_tpl = wwid.config['export_templates']['say']
         
     | 
| 
      
 166 
     | 
    
         
            +
                    tpl = cfg_tpl unless cfg_tpl.nil? || cfg_tpl.empty?
         
     | 
| 
      
 167 
     | 
    
         
            +
                  end
         
     | 
| 
      
 168 
     | 
    
         
            +
                  output = tpl.dup
         
     | 
| 
      
 169 
     | 
    
         
            +
                  output.gsub!(/%date/, date)
         
     | 
| 
      
 170 
     | 
    
         
            +
                  output.gsub!(/%title/, title)
         
     | 
| 
      
 171 
     | 
    
         
            +
                  output.gsub!(/%section/, i.section)
         
     | 
| 
      
 172 
     | 
    
         
            +
                  output.gsub!(/%took/, took || '')
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
                  # Debugging output
         
     | 
| 
      
 175 
     | 
    
         
            +
                  # warn "Saying: #{output}"
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                  # To provide results on the command line after the
         
     | 
| 
      
 178 
     | 
    
         
            +
                  # command runs, add to the wwid.results array. Results
         
     | 
| 
      
 179 
     | 
    
         
            +
                  # are provided on STDERR unless doing is run with
         
     | 
| 
      
 180 
     | 
    
         
            +
                  # `--stdout`
         
     | 
| 
      
 181 
     | 
    
         
            +
                  Doing.logger.info('Spoke the last entry. Did you hear it?')
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                  # This export runs a command for fun, most plugins won't
         
     | 
| 
      
 184 
     | 
    
         
            +
                  voice = wwid.config['plugins']['say']['say_voice'] || 'Alex'
         
     | 
| 
      
 185 
     | 
    
         
            +
                  `say -v "#{voice}" "#{output}"`
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                  # Return the result (don't output to terminal with puts or print)
         
     | 
| 
      
 188 
     | 
    
         
            +
                  output
         
     | 
| 
      
 189 
     | 
    
         
            +
                end
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
                # Register the plugin with doing.
         
     | 
| 
      
 192 
     | 
    
         
            +
                # Doing::Plugins.register 'NAME', TYPE, Class
         
     | 
| 
      
 193 
     | 
    
         
            +
                #
         
     | 
| 
      
 194 
     | 
    
         
            +
                # Name should be lowercase, no spaces
         
     | 
| 
      
 195 
     | 
    
         
            +
                #
         
     | 
| 
      
 196 
     | 
    
         
            +
                # TYPE is :import or :export
         
     | 
| 
      
 197 
     | 
    
         
            +
                #
         
     | 
| 
      
 198 
     | 
    
         
            +
                # Class is the plugin class (e.g. Doing::SayExport), or
         
     | 
| 
      
 199 
     | 
    
         
            +
                # self if called within the class
         
     | 
| 
      
 200 
     | 
    
         
            +
                Doing::Plugins.register 'say', :export, self
         
     | 
| 
      
 201 
     | 
    
         
            +
              end
         
     | 
| 
      
 202 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,169 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            body {
         
     | 
| 
      
 2 
     | 
    
         
            +
              background: #fff;
         
     | 
| 
      
 3 
     | 
    
         
            +
              color: #333;
         
     | 
| 
      
 4 
     | 
    
         
            +
              font-family: Helvetica,arial,freesans,clean,sans-serif;
         
     | 
| 
      
 5 
     | 
    
         
            +
              font-size: 21px;
         
     | 
| 
      
 6 
     | 
    
         
            +
              line-height: 1.5;
         
     | 
| 
      
 7 
     | 
    
         
            +
              text-align: justify;
         
     | 
| 
      
 8 
     | 
    
         
            +
            }
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            @media only screen and (max-width: 900px) {
         
     | 
| 
      
 11 
     | 
    
         
            +
              body {
         
     | 
| 
      
 12 
     | 
    
         
            +
                font-size: calc(12px + 1vw);
         
     | 
| 
      
 13 
     | 
    
         
            +
              }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              .date,
         
     | 
| 
      
 16 
     | 
    
         
            +
              .note {
         
     | 
| 
      
 17 
     | 
    
         
            +
                font-size: calc(8px + 1vw)!important;
         
     | 
| 
      
 18 
     | 
    
         
            +
              }
         
     | 
| 
      
 19 
     | 
    
         
            +
            }
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
            h1 {
         
     | 
| 
      
 22 
     | 
    
         
            +
              margin-bottom: 1em;
         
     | 
| 
      
 23 
     | 
    
         
            +
              margin-left: .1em;
         
     | 
| 
      
 24 
     | 
    
         
            +
              position: relative;
         
     | 
| 
      
 25 
     | 
    
         
            +
              text-align: left;
         
     | 
| 
      
 26 
     | 
    
         
            +
            }
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            ul {
         
     | 
| 
      
 29 
     | 
    
         
            +
              list-style-position: outside;
         
     | 
| 
      
 30 
     | 
    
         
            +
              position: relative;
         
     | 
| 
      
 31 
     | 
    
         
            +
              text-align: left;
         
     | 
| 
      
 32 
     | 
    
         
            +
              padding-left: 0;
         
     | 
| 
      
 33 
     | 
    
         
            +
            }
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            article > ul > li {
         
     | 
| 
      
 36 
     | 
    
         
            +
              display: grid;
         
     | 
| 
      
 37 
     | 
    
         
            +
              grid-template-columns: 14ch auto;
         
     | 
| 
      
 38 
     | 
    
         
            +
              line-height: 1.2;
         
     | 
| 
      
 39 
     | 
    
         
            +
              list-style-type: none;
         
     | 
| 
      
 40 
     | 
    
         
            +
              padding-left: 10px;
         
     | 
| 
      
 41 
     | 
    
         
            +
              position: relative;
         
     | 
| 
      
 42 
     | 
    
         
            +
              word-break: break-word;
         
     | 
| 
      
 43 
     | 
    
         
            +
              transition: background .2s ease-in-out;
         
     | 
| 
      
 44 
     | 
    
         
            +
            }
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            article > ul > li:hover {
         
     | 
| 
      
 47 
     | 
    
         
            +
              background: rgba(150,150,150,.05);
         
     | 
| 
      
 48 
     | 
    
         
            +
            }
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            .date {
         
     | 
| 
      
 51 
     | 
    
         
            +
              color: #7d9ca2;
         
     | 
| 
      
 52 
     | 
    
         
            +
              font-size: 17px;
         
     | 
| 
      
 53 
     | 
    
         
            +
              padding: 15px 1ch 0 0;
         
     | 
| 
      
 54 
     | 
    
         
            +
              text-align: right;
         
     | 
| 
      
 55 
     | 
    
         
            +
              white-space: nowrap;
         
     | 
| 
      
 56 
     | 
    
         
            +
              transition: color .2s ease-in-out;
         
     | 
| 
      
 57 
     | 
    
         
            +
            }
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
            .entry {
         
     | 
| 
      
 60 
     | 
    
         
            +
              border-left: solid 1px #ccc;
         
     | 
| 
      
 61 
     | 
    
         
            +
              line-height: 1.2;
         
     | 
| 
      
 62 
     | 
    
         
            +
              padding: 10px 10px 10px 3ch;
         
     | 
| 
      
 63 
     | 
    
         
            +
              text-indent: -2ch;
         
     | 
| 
      
 64 
     | 
    
         
            +
            }
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
            .tag {
         
     | 
| 
      
 67 
     | 
    
         
            +
              color: #999;
         
     | 
| 
      
 68 
     | 
    
         
            +
              transition: color 1s ease-in;
         
     | 
| 
      
 69 
     | 
    
         
            +
            }
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
            .note {
         
     | 
| 
      
 72 
     | 
    
         
            +
              color: #aaa;
         
     | 
| 
      
 73 
     | 
    
         
            +
              display: block;
         
     | 
| 
      
 74 
     | 
    
         
            +
              font-size: 17px;
         
     | 
| 
      
 75 
     | 
    
         
            +
              line-height: 1.1;
         
     | 
| 
      
 76 
     | 
    
         
            +
              padding: 1em 0 0 2ch;
         
     | 
| 
      
 77 
     | 
    
         
            +
              position: relative;
         
     | 
| 
      
 78 
     | 
    
         
            +
              transition: color .2s ease-in-out;
         
     | 
| 
      
 79 
     | 
    
         
            +
            }
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
            li:hover .note {
         
     | 
| 
      
 82 
     | 
    
         
            +
              color: #777;
         
     | 
| 
      
 83 
     | 
    
         
            +
            }
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
            li:hover .tag {
         
     | 
| 
      
 86 
     | 
    
         
            +
              color: rgb(182, 120, 125);
         
     | 
| 
      
 87 
     | 
    
         
            +
            }
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
            li:hover .date {
         
     | 
| 
      
 90 
     | 
    
         
            +
              color: rgb(100, 169, 165);
         
     | 
| 
      
 91 
     | 
    
         
            +
            }
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
            .note li {
         
     | 
| 
      
 94 
     | 
    
         
            +
                margin-bottom: .5em;
         
     | 
| 
      
 95 
     | 
    
         
            +
                list-style: none;
         
     | 
| 
      
 96 
     | 
    
         
            +
                position: relative;
         
     | 
| 
      
 97 
     | 
    
         
            +
            }
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
            .note li:before {
         
     | 
| 
      
 100 
     | 
    
         
            +
              color: #ddd;
         
     | 
| 
      
 101 
     | 
    
         
            +
              content: '\25BA';
         
     | 
| 
      
 102 
     | 
    
         
            +
              font-size: 12px;
         
     | 
| 
      
 103 
     | 
    
         
            +
              font-weight: 300;
         
     | 
| 
      
 104 
     | 
    
         
            +
              left: -3ch;
         
     | 
| 
      
 105 
     | 
    
         
            +
              position: absolute;
         
     | 
| 
      
 106 
     | 
    
         
            +
              top: .25em;
         
     | 
| 
      
 107 
     | 
    
         
            +
            }
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
            .time {
         
     | 
| 
      
 110 
     | 
    
         
            +
              background: #f9fced;
         
     | 
| 
      
 111 
     | 
    
         
            +
              border-bottom: dashed 1px #ccc;
         
     | 
| 
      
 112 
     | 
    
         
            +
              color: #729953;
         
     | 
| 
      
 113 
     | 
    
         
            +
              font-size: 15px;
         
     | 
| 
      
 114 
     | 
    
         
            +
              margin-right: 4px;
         
     | 
| 
      
 115 
     | 
    
         
            +
              padding: 0 5px;
         
     | 
| 
      
 116 
     | 
    
         
            +
              position: relative;
         
     | 
| 
      
 117 
     | 
    
         
            +
              text-align: right;
         
     | 
| 
      
 118 
     | 
    
         
            +
            }
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
            table td {
         
     | 
| 
      
 121 
     | 
    
         
            +
              border-bottom: solid 1px #ddd;
         
     | 
| 
      
 122 
     | 
    
         
            +
              height: 24px;
         
     | 
| 
      
 123 
     | 
    
         
            +
            }
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            caption {
         
     | 
| 
      
 126 
     | 
    
         
            +
              border-bottom: solid 1px #aaa;
         
     | 
| 
      
 127 
     | 
    
         
            +
              margin: 10px 0;
         
     | 
| 
      
 128 
     | 
    
         
            +
              text-align: left;
         
     | 
| 
      
 129 
     | 
    
         
            +
            }
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
            table {
         
     | 
| 
      
 132 
     | 
    
         
            +
              margin: 50px 0 0 211px;
         
     | 
| 
      
 133 
     | 
    
         
            +
              width: 400px;
         
     | 
| 
      
 134 
     | 
    
         
            +
            }
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
            th {
         
     | 
| 
      
 137 
     | 
    
         
            +
              padding-bottom: 10px;
         
     | 
| 
      
 138 
     | 
    
         
            +
            }
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
            th, td {
         
     | 
| 
      
 141 
     | 
    
         
            +
              padding-right: 20px;
         
     | 
| 
      
 142 
     | 
    
         
            +
            }
         
     | 
| 
      
 143 
     | 
    
         
            +
             
     | 
| 
      
 144 
     | 
    
         
            +
            table {
         
     | 
| 
      
 145 
     | 
    
         
            +
              margin: 50px 0 2em 16ch;
         
     | 
| 
      
 146 
     | 
    
         
            +
              max-width: 400px;
         
     | 
| 
      
 147 
     | 
    
         
            +
            }
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
            .section {
         
     | 
| 
      
 150 
     | 
    
         
            +
              border-left: solid 1px rgb(182, 120, 125);
         
     | 
| 
      
 151 
     | 
    
         
            +
              border-radius: 25px;
         
     | 
| 
      
 152 
     | 
    
         
            +
              border-right: solid 1px rgb(182, 120, 125);
         
     | 
| 
      
 153 
     | 
    
         
            +
              color: rgb(182, 120, 125);
         
     | 
| 
      
 154 
     | 
    
         
            +
              font-size: .8em;
         
     | 
| 
      
 155 
     | 
    
         
            +
              line-height: 1 !important;
         
     | 
| 
      
 156 
     | 
    
         
            +
              padding: 0 4px;
         
     | 
| 
      
 157 
     | 
    
         
            +
              transition: background .4s ease-in, color .4s ease-in;
         
     | 
| 
      
 158 
     | 
    
         
            +
            }
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
            li:hover .section {
         
     | 
| 
      
 161 
     | 
    
         
            +
              color: #fff;
         
     | 
| 
      
 162 
     | 
    
         
            +
              background: rgb(182, 120, 125);
         
     | 
| 
      
 163 
     | 
    
         
            +
            }
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
            a:link {
         
     | 
| 
      
 166 
     | 
    
         
            +
              background-color: rgba(203, 255, 251, .15);
         
     | 
| 
      
 167 
     | 
    
         
            +
              color: #64a9a5;
         
     | 
| 
      
 168 
     | 
    
         
            +
              text-decoration: none;
         
     | 
| 
      
 169 
     | 
    
         
            +
            }
         
     |