├── doing-plugin-capture-thing-import.gemspec └── lib └── doing-plugin-capture-thing-import.rb /doing-plugin-capture-thing-import.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "doing-plugin-capture-thing-import" 3 | s.version = "0.0.2" 4 | s.summary = "Capture Thing import for Doing" 5 | s.description = "Imports entries from the Capture Thing folder" 6 | s.authors = ["Brett Terpstra"] 7 | s.email = "me@brettterpstra.com" 8 | s.files = ["lib/doing-plugin-capture-thing-import.rb"] 9 | s.homepage = "https://brettterpstra.com" 10 | s.license = "MIT" 11 | end 12 | -------------------------------------------------------------------------------- /lib/doing-plugin-capture-thing-import.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # title: Capture Thing Import 4 | # description: Import entries from a Capture Thing folder 5 | # author: Brett Terpstra 6 | # url: https://brettterpstra.com 7 | module Doing 8 | # Capture Thing import plugin 9 | class CaptureThingImport 10 | require 'time' 11 | 12 | include Doing::Util 13 | include Doing::Errors 14 | 15 | def self.settings 16 | { 17 | trigger: '^cap(?:ture)?(:?thing)?' 18 | } 19 | end 20 | 21 | ## 22 | ## Imports a Capture Thing folder 23 | ## 24 | ## @param wwid [WWID] WWID object 25 | ## @param path [String] Path to Capture Thing folder 26 | ## @param options [Hash] Additional Options 27 | ## 28 | def self.import(wwid, path, options: {}) 29 | raise InvalidArgument, 'Path to Capture Thing folder required' if path.nil? 30 | 31 | path = File.expand_path(path) 32 | 33 | raise InvalidArgument, 'File not found' unless File.exist?(path) 34 | 35 | raise InvalidArgument, 'Path is not a directory' unless File.directory?(path) 36 | 37 | options[:no_overlap] ||= false 38 | options[:autotag] ||= wwid.auto_tag 39 | 40 | tags = options[:tag] ? options[:tag].split(/[ ,]+/).map { |t| t.sub(/^@?/, '') } : [] 41 | options[:tag] = nil 42 | prefix = options[:prefix] || '' 43 | 44 | @old_items = wwid.content 45 | 46 | new_items = read_capture_folder(path) 47 | 48 | total = new_items.count 49 | 50 | options[:count] = 0 51 | 52 | new_items = wwid.filter_items(new_items, opt: options) 53 | 54 | skipped = total - new_items.count 55 | Doing.logger.debug('Skipped:' , %(#{skipped} items that didn't match filter criteria)) if skipped.positive? 56 | 57 | imported = [] 58 | 59 | new_items.each do |item| 60 | next if duplicate?(item) 61 | 62 | title = "#{prefix} #{item.title}" 63 | tags.each do |tag| 64 | if title =~ /\b#{tag}\b/i 65 | title.sub!(/\b#{tag}\b/i, "@#{tag}") 66 | else 67 | title += " @#{tag}" 68 | end 69 | end 70 | title = wwid.autotag(title) if options[:autotag] 71 | title.gsub!(/ +/, ' ') 72 | title.strip! 73 | section = options[:section] || item.section 74 | section ||= wwid.config['current_section'] 75 | 76 | new_item = Item.new(item.date, title, section) 77 | new_item.note = item.note 78 | 79 | imported.push(new_item) 80 | end 81 | 82 | dups = new_items.count - imported.count 83 | Doing.logger.info('Skipped:', %(#{dups} duplicate items)) if dups.positive? 84 | 85 | imported = wwid.dedup(imported, no_overlap: !options[:overlap]) 86 | overlaps = new_items.count - imported.count - dups 87 | Doing.logger.debug('Skipped:', "#{overlaps} items with overlapping times") if overlaps.positive? 88 | 89 | imported.each do |item| 90 | wwid.content.add_section(item.section) 91 | wwid.content.push(item) 92 | end 93 | 94 | Doing.logger.info('Imported:', "#{imported.count} items") 95 | end 96 | 97 | def self.duplicate?(item) 98 | @old_items.each do |oi| 99 | return true if item.equal?(oi) 100 | end 101 | 102 | false 103 | end 104 | 105 | def self.parse_entry(date, entry) 106 | lines = entry.strip.split(/\n/) 107 | 108 | return nil if lines.nil? 109 | 110 | time_line = lines.shift 111 | 112 | return nil unless time_line =~ /^# (\d+:\d{2} [AP]M)/ 113 | 114 | m = time_line.match(/^# (\d+:\d{2} [AP]M)/) 115 | 116 | unless m 117 | Doing.logger.debug("Error parsing time #{time_line}") 118 | return nil 119 | end 120 | 121 | time = m[1] 122 | entry_date = Time.parse("#{date} #{time}") 123 | 124 | title = '' 125 | note = Note.new 126 | lines.each_with_index do |l, i| 127 | if l =~ /^-{4,}/ 128 | note.add(lines.slice(i + 1, lines.count - i)) 129 | break 130 | else 131 | title += l 132 | end 133 | end 134 | 135 | Item.new(entry_date, title, nil, note) 136 | end 137 | 138 | def self.read_capture_folder(path) 139 | folder = File.expand_path(path) 140 | 141 | return nil unless File.exist?(folder) && File.directory?(folder) 142 | 143 | items = [] 144 | 145 | files = Dir.glob('**/*.md', base: folder) 146 | 147 | files.each do |file| 148 | date = File.basename(file, '.md').match(/^(\d{4}-\d{2}-\d{2})/)[1] 149 | input = IO.read(File.join(folder, file)) 150 | input = input.force_encoding('utf-8') if input.respond_to? :force_encoding 151 | entries = input.split(/^\* \* \* \* \*$/).map(&:strip).delete_if(&:empty?) 152 | 153 | entries.each do |entry| 154 | new_entry = parse_entry(date, entry) 155 | items << new_entry if new_entry 156 | end 157 | end 158 | 159 | items 160 | end 161 | 162 | Doing::Plugins.register 'capturething', :import, self 163 | end 164 | end 165 | --------------------------------------------------------------------------------