Class: Rubyfocus::Document

Inherits:
Object
  • Object
show all
Includes:
Searchable
Defined in:
lib/rubyfocus/document.rb

Overview

The Document is how rubyfocus stores an OmniFocus document, both locally and otherwise. A Document contains a number of arrays of contexts, settings, folders, projects, and tasks, and is also able to keep track of what patch it’s up to, for updating.

You can initialize a document through Document.new(doc), where doc is either an XML string, or a Nokogiri XML document (or nil). Alternatively, you can initialize through Document.from_file(file), which reads the file and parses it as XML

You add XML to the document by running Document::apply_xml(doc), which takes all children of the root XML node, tries to turn each child into a relevant object, and adds it to the document. This is done using the private ivar_for method, as well as add_element(e), which you can use to add individual objects.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Searchable

#find, #select

Constructor Details

#initialize(doc = nil) ⇒ Document

Initalise with one of:

  • a Nokogiri document

  • a string

  • a fetcher subclass



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/rubyfocus/document.rb', line 28

def initialize(doc=nil)
  %w(contexts settings projects folders tasks).each{ |s| instance_variable_set("@#{s}", Rubyfocus::SearchableArray.new) }

  if doc
    if doc.is_a?(String)
      apply_xml(Nokogiri::XML(doc))
    elsif doc.is_a?(Nokogiri::XML)
      apply_xml(doc)
    elsif doc.kind_of?(Rubyfocus::Fetcher)
      self.fetcher = doc
      base = Nokogiri::XML(doc.base)
      self.apply_xml(base)
      self.patch_id = doc.base_id
    end
  end
end

Instance Attribute Details

#contextsObject (readonly)

A number of arrays into which elements may fit



15
16
17
# File 'lib/rubyfocus/document.rb', line 15

def contexts
  @contexts
end

#fetcherObject

This is the fetcher object, used to fetch new data



22
23
24
# File 'lib/rubyfocus/document.rb', line 22

def fetcher
  @fetcher
end

#foldersObject (readonly)

A number of arrays into which elements may fit



15
16
17
# File 'lib/rubyfocus/document.rb', line 15

def folders
  @folders
end

#patch_idObject

This is the identifier of the current patch level. This also determines which patches can be applied to the current document.



19
20
21
# File 'lib/rubyfocus/document.rb', line 19

def patch_id
  @patch_id
end

#projectsObject (readonly)

A number of arrays into which elements may fit



15
16
17
# File 'lib/rubyfocus/document.rb', line 15

def projects
  @projects
end

#settingsObject (readonly)

A number of arrays into which elements may fit



15
16
17
# File 'lib/rubyfocus/document.rb', line 15

def settings
  @settings
end

#tasksObject (readonly)

A number of arrays into which elements may fit



15
16
17
# File 'lib/rubyfocus/document.rb', line 15

def tasks
  @tasks
end

Class Method Details

.from_localObject

Initialize from the local repo



51
52
53
# File 'lib/rubyfocus/document.rb', line 51

def self.from_local
  new(Rubyfocus::LocalFetcher.new)
end

.from_url(url) ⇒ Object

Initialize with a URL, for remote fetching. Not implemented yet TODO implement

Raises:

  • (RuntimeError)


57
58
59
60
# File 'lib/rubyfocus/document.rb', line 57

def self.from_url(url)
  raise RuntimeError, "Rubyfocus::Document.from_url not yet implemented."
  # new(Rubyfocus::RemoteFetcher.new(url))
end

.from_xml(file) ⇒ Object

…or from file! If you provide it with an XML file, it’ll load up without a fetcher.



46
47
48
# File 'lib/rubyfocus/document.rb', line 46

def self.from_xml(file)
  new(File.read(file))
end

.load_from_file(file_location) ⇒ Object

Load from a a hash



63
64
65
66
67
# File 'lib/rubyfocus/document.rb', line 63

def self.load_from_file(file_location)
  d = YAML::load_file(file_location)
  d.fetcher.reset
  d
end

Instance Method Details

#[](search_id) ⇒ Object


Find elements from id



182
183
184
# File 'lib/rubyfocus/document.rb', line 182

def [] search_id
  self.elements.find{ |elem| elem.id == search_id }
end

#add_element(e, overwrite: false) ⇒ Object

Add an element. Element should be a Project, Task, Context, Folder, or Setting. If overwrite set to false and ID already occurs in the document, throw an error. If ID is nil, throw an error.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/rubyfocus/document.rb', line 102

def add_element(e, overwrite:false)
  # Error check
  raise(Rubyfocus::DocumentElementException, "Adding element to document, but it has no ID.") if e.id.nil?
  raise(Rubyfocus::DocumentElementException, "Adding element to document, but element with this ID already exists.") if !overwrite && has_id?(e.id)

  # Otherwise, full steam ahead
  e.document = self

  if (dupe_element = self[e.id]) && overwrite
    remove_element(dupe_element)
  end

  # Add to the correct array
  dest = ivar_for(e)
  if dest
    dest << e
  else
    raise ArgumentError, "You passed a #{e.class} to Document#add_element - I don't know what to do with this."
  end
end

#apply_xml(doc) ⇒ Object


Apply XML!



81
82
83
84
85
# File 'lib/rubyfocus/document.rb', line 81

def apply_xml(doc)
  doc.root.children.select{ |e| !e.text? }.each do |node|
    elem = Rubyfocus::Parser.parse(self, node)
  end
end

#elementsObject Also known as: array


Searchable stuff



172
173
174
# File 'lib/rubyfocus/document.rb', line 172

def elements
  @tasks + @projects + @contexts + @folders + @settings
end

#has_id?(id) ⇒ Boolean

Check if the document has an element of a given ID

Returns:

  • (Boolean)


187
188
189
# File 'lib/rubyfocus/document.rb', line 187

def has_id?(id)
  self.elements.any?{ |e| e.id == id }
end

#remove_element(e) ⇒ Object

Remove an element from the document.



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/rubyfocus/document.rb', line 124

def remove_element(e)
  e = self[e] if e.is_a?(String)
  return if e.nil?

  e.document = nil

  dest = ivar_for(e)
  if dest
    dest.delete(e)
  else
    raise ArgumentError, "You passed a #{e.class} to Document#remove_element - I don't know what to do with this."  
  end
end

#save(file) ⇒ Object


YAML export



194
195
196
# File 'lib/rubyfocus/document.rb', line 194

def save(file)
  File.open(file, "w"){ |io| io.puts YAML::dump(self) }
end

#updateObject


Use the linked fetcher to update the document



71
72
73
74
75
76
77
# File 'lib/rubyfocus/document.rb', line 71

def update
  if fetcher
    fetcher.update_full(self)
  else
    raise RuntimeError, "Tried to update a document with no fetcher."
  end
end

#update_element(node) ⇒ Object

Update an element in-place by applying xml. This method also takes into account:

  • new nodes (i.e. silently creates if required)

  • tasks upgraded to projects

  • projects downgraded to tasks

Note that unlike add_element, this takes pure XML



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/rubyfocus/document.rb', line 143

def update_element(node)
  element = self[node["id"]]

  # Does element already exist?
  if element
    # Quick check: is it a task being upgraded to a project?
    if element.class == Rubyfocus::Task && Rubyfocus::Project.matches_node?(node)
      # Upgrade
      new_node = element.to_project
      new_node.apply_xml(node)
      add_element(new_node, overwrite:true)
    # or is the project being downgraded to a task?
    elsif element.class == Rubyfocus::Project && !Rubyfocus::Project.matches_node?(node)
      # Downgrade
      new_node = element.to_task
      new_node.apply_xml(node)
      add_element(new_node, overwrite:true)
    else
      # Update in-place
      element.apply_xml(node)
    end
  else
    # Create a new node and add it
    Rubyfocus::Parser.parse(self,node)
  end
end