class Rubyfocus::Document
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.
Attributes
A number of arrays into which elements may fit
This is the fetcher object, used to fetch new data
A number of arrays into which elements may fit
This is the identifier of the current patch level. This also determines which patches can be applied to the current document.
A number of arrays into which elements may fit
A number of arrays into which elements may fit
A number of arrays into which elements may fit
Public Class Methods
Initialize from the local repo
# File lib/rubyfocus/document.rb, line 51 def self.from_local new(Rubyfocus::LocalFetcher.new) end
Initialize with a URL, for remote fetching. Not implemented yet TODO implement
# 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
…or from file! If you provide it with an XML file, it'll load up without a fetcher.
# File lib/rubyfocus/document.rb, line 46 def self.from_xml(file) new(File.read(file)) end
Load from a a hash
# 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
Initalise with one of:
-
a Nokogiri document
-
a string
-
a fetcher subclass
# 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
Public Instance Methods
# File lib/rubyfocus/document.rb, line 209 def [] search_id self.elements.find{ |elem| elem.id == search_id } end
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.
# File lib/rubyfocus/document.rb, line 103 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
# File lib/rubyfocus/document.rb, line 82 def apply_xml(doc) doc.root.children.select{ |e| !e.text? }.each do |node| elem = Rubyfocus::Parser.parse(self, node) end end
# File lib/rubyfocus/document.rb, line 199 def elements @tasks + @projects + @contexts + @folders + @settings end
Check if the document has an element of a given ID
# File lib/rubyfocus/document.rb, line 214 def has_id?(id) self.elements.any?{ |e| e.id == id } end
Update an element in-place by creating a new element, deleting the old, and adding the new. This method is chiefly used for patching OF documents using V1 patches. Properties not explicitly mentioned in the patch are reverted to their default values. This method also takes into account:
-
new nodes (i.e. silently creates if required)
-
tasks upgraded to projects (if task has a <project> element)
-
projects downgraded to tasks (if project has no <project> element)
Note that unlike add_element
, this takes pure XML
# File lib/rubyfocus/document.rb, line 190 def overwrite_element(node) element = self[node["id"]] self.remove_element(element) if element Rubyfocus::Parser.parse(self, node) end
Remove an element from the document.
# File lib/rubyfocus/document.rb, line 125 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
# File lib/rubyfocus/document.rb, line 221 def save(file) File.open(file, "w"){ |io| io.puts YAML::dump(self) } end
# File lib/rubyfocus/document.rb, line 71 def update if fetcher raise RuntimeError, "Rubyfocus cannot currently read encrypted databases." if fetcher.encrypted? fetcher.update_full(self) else raise RuntimeError, "Tried to update a document with no fetcher." end end
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 (if task has a non-empty <project> element)
-
projects downgraded to tasks (if project has an empty <project> element)
Note that unlike add_element
, this takes pure XML
# File lib/rubyfocus/document.rb, line 144 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? # Upgrade criteria: non-empty project tag if( element.class == Rubyfocus::Task && (node / "project *").size > 0 ) # 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? # Downgrade criteria: presence of an empty project tag elsif( element.class == Rubyfocus::Project && (node / "project").size > 0 && (node / "project *").size == 0 ) # 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
Private Instance Methods
Given an object, work out the correct instance variable for it to go into
# File lib/rubyfocus/document.rb, line 89 def ivar_for(obj) { Rubyfocus::Project => @projects, Rubyfocus::Context => @contexts, Rubyfocus::Task => @tasks, Rubyfocus::Folder => @folders, Rubyfocus::Setting => @settings }[obj.class] end