Class: Doing::Items

Inherits:
Array show all
Defined in:
lib/doing/items/items.rb,
lib/doing/items/util.rb,
lib/doing/items/filter.rb,
lib/doing/items/modify.rb,
lib/doing/items/sections.rb

Overview

A collection of Item objects

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Array

#cap_first, #good?, #utf8

Methods included from ChronifyArray

#time_string, #to_abbr, #to_natural, #to_years

Constructor Details

#initializeItems

Returns a new instance of Items.



13
14
15
16
# File 'lib/doing/items/items.rb', line 13

def initialize
  super
  @sections = []
end

Instance Attribute Details

#sectionsObject

Returns the value of attribute sections.



11
12
13
# File 'lib/doing/items/items.rb', line 11

def sections
  @sections
end

Instance Method Details

#add_section(section, log: false) ⇒ Object

Add a new section to the sections array. Accepts either a Section object, or a title string that will be converted into a Section.

Parameters:

  • section (Section)

    The section to add. A String value will be converted to Section automatically.

  • log (Boolean) (defaults to: false)

    Add a log message notifying the user about the creation of the section.

Returns:

  • nothing



59
60
61
62
63
64
65
66
# File 'lib/doing/items/sections.rb', line 59

def add_section(section, log: false)
  section = section.is_a?(Section) ? section : Section.new(section.cap_first)

  return if section?(section)

  @sections.push(section)
  Doing.logger.info('New section:', %("#{section}" added)) if log
end

#all_tagsArray

Get all tags on Items in self

Returns:

  • (Array)

    array of tags



26
27
28
29
30
# File 'lib/doing/items/util.rb', line 26

def all_tags
  each_with_object([]) do |entry, tags|
    tags.concat(entry.tags).sort!.uniq!
  end
end

#between_dates(start, finish) ⇒ Items

Filter Items by date. String arguments will be chronified

Parameters:

  • start (Time, String)

    Filter items after this date

  • finish (Time, String)

    Filter items before this date

Returns:

  • (Items)

    array of items with dates between targets



62
63
64
65
66
# File 'lib/doing/items/filter.rb', line 62

def between_dates(start, finish)
  start = start.chronify(guess: :begin, future: false) if start.is_a?(String)
  finish = finish.chronify(guess: :end) if finish.is_a?(String)
  WWID.new.filter_items(self, opt: { date_filter: [start, finish] })
end

#dedup(match_section: true) ⇒ Items

Remove duplicated entries. Duplicate entries must have matching start date, title, note, and section

Returns:

  • (Items)

    Items array with duplicate entries removed



60
61
62
63
64
65
66
67
# File 'lib/doing/items/util.rb', line 60

def dedup(match_section: true)
  unique = Items.new
  each do |item|
    unique.push(item) unless unique.include?(item, match_section: match_section)
  end

  unique
end

#dedup!(match_section: true) ⇒ Object

See Also:



70
71
72
# File 'lib/doing/items/util.rb', line 70

def dedup!(match_section: true)
  replace dedup(match_section: match_section)
end

#delete(item) ⇒ Object

Create a deep copy of Items

def clone Marshal.load(Marshal.dump(self)) end



10
11
12
13
14
15
16
17
18
19
# File 'lib/doing/items/util.rb', line 10

def delete(item)
  deleted = nil
  each_with_index do |i, idx|
    if i.equal?(item, match_section: true)
      deleted = delete_at(idx)
      break
    end
  end
  deleted
end

#delete_item(item, single: false) ⇒ Object

Delete an item from the index

Parameters:

  • item

    The item



10
11
12
13
14
15
# File 'lib/doing/items/modify.rb', line 10

def delete_item(item, single: false)
  deleted = delete(item)
  Doing.logger.count(:deleted)
  Doing.logger.info('Entry deleted:', deleted.title) if single
  deleted
end

#delete_section(section, log: false) ⇒ Object

Raises:

  • (DoingRuntimeError)


68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/doing/items/sections.rb', line 68

def delete_section(section, log: false)
  return unless section?(section)

  raise DoingRuntimeError, 'Section not empty' if in_section(section).count.positive?

  @sections.each do |sect|
    next unless sect.title == section && in_section(sect).count.zero?

    @sections.delete(sect)
    Doing.logger.info('Removed section:', %("#{section}" removed)) if log
  end

  Doing.logger.error('Not found:', %("#{section}" not found))
end

#diff(items) ⇒ Hash

Return Items containing items that don't exist in receiver

Parameters:

  • items (Items)

    Receiver

Returns:

  • (Hash)

    Hash of added and deleted items



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/doing/items/util.rb', line 40

def diff(items)
  a = clone
  b = items.clone

  a.delete_if do |item|
    if b.include?(item)
      b.delete(item)
      true
    else
      false
    end
  end
  { added: b, deleted: a }
end

#find_id(id) ⇒ Object

Find an item by ID

Parameters:

  • id

    The identifier to match



42
43
44
# File 'lib/doing/items/items.rb', line 42

def find_id(id)
  select { |item| item.id == id }[0]
end

#guess_section(frag, distance: 2) ⇒ Section

Return the best section match for a search query

Parameters:

  • frag

    The search query

  • distance (defaults to: 2)

    The distance apart characters can be (fuzziness)

Returns:

  • (Section)

    (first) matching section object



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/doing/items/sections.rb', line 32

def guess_section(frag, distance: 2)
  section = nil
  re = frag.to_rx(distance: distance, case_type: :ignore)
  @sections.each do |sect|
    next unless sect.title =~ /#{re}/i

    Doing.logger.debug('Match:', %(Assuming "#{sect.title}" from "#{frag}"))
    section = sect
    break
  end

  section
end

#in_section(section) ⇒ Items

Get a new Items object containing only items in a specified section

Parameters:

  • section (String)

    section title

Returns:

  • (Items)

    Array of items



12
13
14
15
16
17
18
19
20
21
# File 'lib/doing/items/filter.rb', line 12

def in_section(section)
  sect = section.is_a?(Section) ? section.title : section
  if sect =~ /^all$/i
    dup
  else
    items = Items.new.concat(select { |item| !item.nil? && item.section == section })
    items.add_section(section, log: false)
    items
  end
end

#include?(item, match_section: true) ⇒ Boolean

Test if self includes Item

Parameters:

  • item (Item)

    The item to search for

  • match_section (Boolean) (defaults to: true)

    Section must match

Returns:

  • (Boolean)

    True if Item exists



26
27
28
29
30
31
32
33
34
35
36
# File 'lib/doing/items/items.rb', line 26

def include?(item, match_section: true)
  includes = false
  each do |other_item|
    if other_item.equal?(item, match_section: match_section)
      includes = true
      break
    end
  end

  includes
end

#index_for_id(id) ⇒ Object

Return the index for an entry matching ID

Parameters:

  • id

    The identifier to match



51
52
53
54
55
56
57
58
59
60
# File 'lib/doing/items/items.rb', line 51

def index_for_id(id)
  i = nil
  each_with_index do |item, idx|
    if item.id == id
      i = idx
      break
    end
  end
  i
end

#search(query, case_type: :smart) ⇒ Items

Search Items for a string (title and note)

Parameters:

  • query (String)

    The query

  • case_type (Symbol) (defaults to: :smart)

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

Returns:

  • (Items)

    array of items matching search



32
33
34
# File 'lib/doing/items/filter.rb', line 32

def search(query, case_type: :smart)
  WWID.new.fuzzy_filter_items(self, query, case_type: case_type)
end

#section?(section) ⇒ Boolean

Test if section already exists

Parameters:

  • section (String)

    section title

Returns:

  • (Boolean)

    true if section exists



19
20
21
22
# File 'lib/doing/items/sections.rb', line 19

def section?(section)
  section = section.is_a?(Section) ? section.title.downcase : section.downcase
  @sections.map { |i| i.title.downcase }.include?(section)
end

#section_titlesArray

List sections, title only

Returns:

  • (Array)

    section titles



9
10
11
# File 'lib/doing/items/sections.rb', line 9

def section_titles
  @sections.map(&:title)
end

#tagged(tags, bool: :and) ⇒ Items

Search items by tags

Parameters:

  • tags (Array, String)

    The tags by which to filter

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

    The bool with which to combine multiple tags

Returns:

  • (Items)

    array of items matching tag filter



46
47
48
# File 'lib/doing/items/filter.rb', line 46

def tagged(tags, bool: :and)
  WWID.new.filter_items(self, opt: { tag: tags, bool: bool })
end

#to_sObject

Output sections and items in Doing file format



63
64
65
66
67
68
69
70
71
72
73
# File 'lib/doing/items/items.rb', line 63

def to_s
  out = []
  @sections.each do |section|
    out.push(section.original)
    items = in_section(section.title).sort_by { |i| [i.date, i.title] }
    items.reverse! if Doing.setting('doing_file_sort').normalize_order == :desc
    items.each { |item| out.push(item.to_s) }
  end

  out.join("\n")
end

#update_item(old_item, new_item) ⇒ Object

Update an item in the index with a modified item

Parameters:

  • old_item

    The old item

  • new_item

    The new item

Raises:

  • (ItemNotFound)


23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/doing/items/modify.rb', line 23

def update_item(old_item, new_item)
  s_idx = index { |item| item.equal?(old_item) }

  raise ItemNotFound, 'Unable to find item in index, did it mutate?' unless s_idx

  return if fetch(s_idx).equal?(new_item)

  self[s_idx] = new_item
  Doing.logger.count(:updated)
  Doing.logger.info('Entry updated:', self[s_idx].title.trunc(60))
  new_item
end