Class: Doing::Item

Inherits:
Object
  • Object
show all
Defined in:
lib/doing/item.rb

Overview

This class describes a single WWID item

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(date, title, section, note = nil) ⇒ Item

Initialize an item with date, title, section, and optional note

Parameters:

  • date (Time)

    The item's start date

  • title (String)

    The title

  • section (String)

    The section to which the item belongs

  • note (Array or String) (defaults to: nil)

    The note (optional)



23
24
25
26
27
28
# File 'lib/doing/item.rb', line 23

def initialize(date, title, section, note = nil)
  @date = date.is_a?(Time) ? date : Time.parse(date)
  @title = title
  @section = section
  @note = Note.new(note)
end

Instance Attribute Details

#dateObject

Returns the value of attribute date.



8
9
10
# File 'lib/doing/item.rb', line 8

def date
  @date
end

#noteObject

Returns the value of attribute note.



8
9
10
# File 'lib/doing/item.rb', line 8

def note
  @note
end

#sectionObject

Returns the value of attribute section.



8
9
10
# File 'lib/doing/item.rb', line 8

def section
  @section
end

#titleObject

Returns the value of attribute title.



8
9
10
# File 'lib/doing/item.rb', line 8

def title
  @title
end

Instance Method Details

#calculate_end_date(opt) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/doing/item.rb', line 60

def calculate_end_date(opt)
  if opt[:took]
    if @date + opt[:took] > Time.now
      @date = Time.now - opt[:took]
      Time.now
    else
      @date + opt[:took]
    end
  elsif opt[:back]
    if opt[:back].is_a? Integer
      @date + opt[:back]
    else
      @date + (opt[:back] - @date)
    end
  else
    Time.now
  end
end

#durationObject

If the entry doesn't have a @done date, return the elapsed time



35
36
37
38
39
# File 'lib/doing/item.rb', line 35

def duration
  return nil if @title =~ /(?<=^| )@done\b/

  return Time.now - @date
end

#end_dateTime

Get the value of the item's @done tag

Returns:

  • (Time)

    @done value



56
57
58
# File 'lib/doing/item.rb', line 56

def end_date
  @end_date ||= Time.parse(Regexp.last_match(1)) if @title =~ /@done\((\d{4}-\d\d-\d\d \d\d:\d\d.*?)\)/
end

#equal?(other) ⇒ Boolean

Test for equality between items

Parameters:

  • other (Item)

    The other item

Returns:

  • (Boolean)

    is equal?



93
94
95
96
97
98
99
100
101
# File 'lib/doing/item.rb', line 93

def equal?(other)
  return false if @title.strip != other.title.strip

  return false if @date != other.date

  return false unless @note.equal?(other.note)

  true
end

#expand_date_tags(additional_tags = nil) ⇒ Object

Updates the title of the Item by expanding natural language dates within configured date tags (tags whose value is expected to be a date)

Parameters:

  • additional_tags (defaults to: nil)

    An array of additional tag names to consider dates



143
144
145
# File 'lib/doing/item.rb', line 143

def expand_date_tags(additional_tags = nil)
  @title.expand_date_tags(additional_tags)
end

#idString

Generate a hash that represents the entry

Returns:



82
83
84
# File 'lib/doing/item.rb', line 82

def id
  @id ||= (@date.to_s + @title + @section).hash
end

#ignore_case(search, case_type) ⇒ Boolean

Determine if case should be ignored for searches

Parameters:

  • search (String)

    The search string

  • case_type (Symbol)

    The case type

Returns:

  • (Boolean)

    case should be ignored



268
269
270
# File 'lib/doing/item.rb', line 268

def ignore_case(search, case_type)
  (case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore
end

#intervalObject

Get the difference between the item's start date and the value of its @done tag (if present)

Returns:

  • Interval in seconds



47
48
49
# File 'lib/doing/item.rb', line 47

def interval
  @interval ||= calc_interval
end

#move_to(new_section, label: true, log: true) ⇒ Object

Move item from current section to destination section

Parameters:

  • new_section (String)

    The destination section

  • label (Boolean) (defaults to: true)

    add @from(original section) tag

  • log (Boolean) (defaults to: true)

    log this action

Returns:

  • nothing



358
359
360
361
362
363
364
365
366
367
368
# File 'lib/doing/item.rb', line 358

def move_to(new_section, label: true, log: true)
  from = @section

  tag('from', rename_to: 'from', value: from, force: true) if label
  @section = new_section

  Doing.logger.count(@section == 'Archive' ? :archived : :moved) if log
  Doing.logger.debug("#{@section == 'Archive' ? 'Archived' : 'Moved'}:",
                     "#{@title.truncate(60)} from #{from} to #{@section}")
  self
end

#overlapping_time?(item_b) ⇒ Boolean

Test if the interval between start date and @done value overlaps with another item's

Parameters:

  • item_b (Item)

    The item to compare

Returns:

  • (Boolean)

    overlaps?



122
123
124
125
126
127
128
129
130
131
132
# File 'lib/doing/item.rb', line 122

def overlapping_time?(item_b)
  return true if same_time?(item_b)

  start_a = date
  interval = interval
  end_a = interval ? start_a + interval.to_i : start_a
  start_b = item_b.date
  interval = item_b.interval
  end_b = interval ? start_b + interval.to_i : start_b
  (start_a >= start_b && start_a <= end_b) || (end_a >= start_b && end_a <= end_b) || (start_a < start_b && end_a > end_b)
end

#same_time?(item_b) ⇒ Boolean

Test if two items occur at the same time (same start date and equal duration)

Parameters:

  • item_b (Item)

    The item to compare

Returns:

  • (Boolean)

    is equal?



110
111
112
# File 'lib/doing/item.rb', line 110

def same_time?(item_b)
  date == item_b.date ? interval == item_b.interval : false
end

#search(search, distance: nil, negate: false, case_type: nil) ⇒ Boolean

Test if item matches search string

Parameters:

  • search (String)

    The search string

  • negate (Boolean) (defaults to: false)

    negate results

  • case_type (Symbol) (defaults to: nil)

    The case-sensitivity type (:sensitive, :ignore, :smart)

Returns:

  • (Boolean)

    matches search criteria



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/doing/item.rb', line 283

def search(search, distance: nil, negate: false, case_type: nil)
  prefs = Doing.config.settings['search'] || {}
  matching = prefs.fetch('matching', 'pattern').normalize_matching
  distance ||= prefs.fetch('distance', 3).to_i
  case_type ||= prefs.fetch('case', 'smart').normalize_case

  if search.is_rx? || matching == :fuzzy
    matches = @title + @note.to_s =~ search.to_rx(distance: distance, case_type: case_type)
  else
    query = to_phrase_query(search.strip)

    if query[:must].nil? && query[:must_not].nil?
      query[:must] = query[:should]
      query[:should] = []
    end
    matches = no_searches?(query[:must_not], case_type: case_type)
    matches &&= all_searches?(query[:must], case_type: case_type)
    matches &&= any_searches?(query[:should], case_type: case_type)
  end
  # if search =~ /(?<=\A| )[+-]\S/
  # else
  #   text = @title + @note.to_s
  #   matches = text =~ search.to_rx(distance: distance, case_type: case_type)
  # end

  # if search.is_rx? || !fuzzy
  #   matches = text =~ search.to_rx(distance: distance, case_type: case_type)
  # else
  #   distance = 0.25 if distance > 1
  #   score = if (case_type == :smart && search !~ /[A-Z]/) || case_type == :ignore
  #             text.downcase.pair_distance_similar(search.downcase)
  #           else
  #             score = text.pair_distance_similar(search)
  #           end

  #   if score >= distance
  #     matches = true
  #     Doing.logger.debug('Fuzzy Match:', %(#{@title}, "#{search}" #{score}))
  #   end
  # end

  negate ? !matches : matches
end

#should_finish?Boolean

Test if item is included in never_finish config and thus should not receive a @done tag

Returns:

  • (Boolean)

    item should receive @done tag



333
334
335
# File 'lib/doing/item.rb', line 333

def should_finish?
  should?('never_finish')
end

#should_time?Boolean

Test if item is included in never_time config and thus should not receive a date on the @done tag

Returns:

  • (Boolean)

    item should receive @done date



343
344
345
# File 'lib/doing/item.rb', line 343

def should_time?
  should?('never_time')
end

#tag(tags, **options) ⇒ Object

Add (or remove) tags from the title of the item

Parameters:

  • tags (Array)

    The tags to apply

  • options

    Additional options

Options Hash (**options):

  • :date (Boolean)

    Include timestamp?

  • :single (Boolean)

    Log as a single change?

  • :value (String)

    A value to include as @tag(value)

  • :remove (Boolean)

    if true remove instead of adding

  • :rename_to (String)

    if not nil, rename target tag to this tag name

  • :regex (Boolean)

    treat target tag string as regex pattern

  • :force (Boolean)

    with rename_to, add tag if it doesn't exist



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/doing/item.rb', line 161

def tag(tags, **options)
  added = []
  removed = []

  date = options.fetch(:date, false)
  options[:value] ||= date ? Time.now.strftime('%F %R') : nil
  options.delete(:date)

  single = options.fetch(:single, false)
  options.delete(:single)

  tags = tags.to_tags if tags.is_a? ::String

  remove = options.fetch(:remove, false)
  tags.each do |tag|
    bool = remove ? :and : :not
    if tags?(tag, bool)
      @title.tag!(tag, **options).strip!
      remove ? removed.push(tag) : added.push(tag)
    end
  end

  Doing.logger.log_change(tags_added: added, tags_removed: removed, count: 1, item: self, single: single)

  self
end

#tag_arrayArray

convert tags on item to an array with @ symbols removed

Returns:

  • (Array)

    array of tags



202
203
204
# File 'lib/doing/item.rb', line 202

def tag_array
  tags.tags_to_array
end

#tag_values?(queries, bool = :and, negate: false) ⇒ Boolean

Test if item matches tag values

Parameters:

  • queries (Array)

    The tag value queries to test

  • bool (Symbol) (defaults to: :and)

    The boolean to use for multiple tags (:and, :or, :not)

  • negate (Boolean) (defaults to: false)

    negate the result?

Returns:

  • (Boolean)

    true if tag/bool combination passes



246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/doing/item.rb', line 246

def tag_values?(queries, bool = :and, negate: false)
  bool = bool.normalize_bool

  matches = case bool
            when :and
              all_values?(queries)
            when :not
              no_values?(queries)
            else
              any_values?(queries)
            end
  negate ? !matches : matches
end

#tagsArray

Get a list of tags on the item

Returns:

  • (Array)

    array of tags (no values)



193
194
195
# File 'lib/doing/item.rb', line 193

def tags
  @title.scan(/(?<= |\A)@([^\s(]+)/).map { |tag| tag[0] }.sort.uniq
end

#tags?(tags, bool = :and, negate: false) ⇒ Boolean

Test if item contains tag(s)

Parameters:

  • tags (Array or String)

    The tags to test. Can be an array or a comma-separated string.

  • bool (Symbol) (defaults to: :and)

    The boolean to use for multiple tags (:and, :or, :not)

  • negate (Boolean) (defaults to: false)

    negate the result?

Returns:

  • (Boolean)

    true if tag/bool combination passes



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/doing/item.rb', line 215

def tags?(tags, bool = :and, negate: false)
  if bool == :pattern
    tags = tags.join(' ') if tags.is_a?(Array)
    matches = tag_pattern?(tags.gsub(/ *, */, ' '))

    return negate ? !matches : matches
  end

  tags = split_tags(tags)
  bool = bool.normalize_bool

  matches = case bool
            when :and
              all_tags?(tags)
            when :not
              no_tags?(tags)
            else
              any_tags?(tags)
            end
  negate ? !matches : matches
end

#to_sObject

outputs item in Doing file format, including leading tab



371
372
373
# File 'lib/doing/item.rb', line 371

def to_s
  "\t- #{@date.strftime('%Y-%m-%d %H:%M')} | #{@title}#{@note.empty? ? '' : "\n#{@note}"}"
end