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

contexts[R]

A number of arrays into which elements may fit

fetcher[RW]

This is the fetcher object, used to fetch new data

folders[R]

A number of arrays into which elements may fit

patch_id[RW]

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

projects[R]

A number of arrays into which elements may fit

settings[R]

A number of arrays into which elements may fit

tasks[R]

A number of arrays into which elements may fit

Public Class Methods

from_local() click to toggle source

Initialize from the local repo

# File lib/rubyfocus/document.rb, line 51
def self.from_local
        new(Rubyfocus::LocalFetcher.new)
end
from_url(url) click to toggle source

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
from_xml(file) click to toggle source

…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_file(file_location) click to toggle source

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
new(doc=nil) click to toggle source

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

[](search_id) click to toggle source
# File lib/rubyfocus/document.rb, line 209
def [] search_id
        self.elements.find{ |elem| elem.id == search_id }
end
add_element(e, overwrite:false) click to toggle source

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
apply_xml(doc) click to toggle source
# 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
array()

For Searchable include

Alias for: elements
elements() click to toggle source
# File lib/rubyfocus/document.rb, line 199
def elements
        @tasks + @projects + @contexts + @folders + @settings
end
Also aliased as: array
has_id?(id) click to toggle source

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
overwrite_element(node) click to toggle source

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_element(e) click to toggle source

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
save(file) click to toggle source
# File lib/rubyfocus/document.rb, line 221
def save(file)
        File.open(file, "w"){ |io| io.puts YAML::dump(self) }
end
update() click to toggle source
# 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_element(node) click to toggle source

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

ivar_for(obj) click to toggle source

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