rubyfocus 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 61a752ab5129ed61bbe2f963646d66ef2e1b3b97
4
- data.tar.gz: e19a2a1e78254bd70b72379ddf4bcd77520e9c04
3
+ metadata.gz: 63565e849764d58187bc270c714a509aec780ff5
4
+ data.tar.gz: 91c1408da7a1764a625d0ebd1434441dde52cd79
5
5
  SHA512:
6
- metadata.gz: 663d2e0ca73d48a96079b9bec2435226ff8bb7d807abb7a9e8d4d7c86b0ea9038f458722d8e8692f5cd411f50bfbaf6c7428cc4976f1e26bdcf6cab7f3b5a2da
7
- data.tar.gz: 81d92aab4bc7fce3d8d6d376836c6e7154aceae519a143c6782d61c1dd4426b5e678e2bf634d75b3825d0cb3d5fb74597baf6c48e2251aa1565b4e93062478d0
6
+ metadata.gz: 2f2925e0baae0e08eaf043a7dea9120446ab15b99729f9319c3c47e73feafe7246262879fa409734f0fce6e7566571a3711e3023d56217ec83af77a33ebdaad7
7
+ data.tar.gz: 119909f3d146a97f0d4cd7b3fd0c1afff0425220c8acfd235f6635609a3fe2fdfd33f3106bf51cbc85481a65c43dc991a01f68cf1f4d5110e91b5d2421c10166
data/README.md CHANGED
@@ -1,8 +1,14 @@
1
+ # Version: 0.4.0
2
+
1
3
  Rubyfocus is a one-way (read-only) ruby bridge to OmniFocus. Analyse, store, inspect, or play with your projects and tasks in OmniFocus from the comfort and flexibility of ruby!
2
4
 
3
5
  # Installation
4
6
 
5
- Rubyfocus is a ruby gem. It's not currently hosted on rubygems, though, so you'll have to download and install locally.
7
+ ## Via rubygems
8
+
9
+ ```
10
+ gem install rubyfocus
11
+ ```
6
12
 
7
13
  ## Via git
8
14
 
@@ -16,7 +22,7 @@ Now build and install it!
16
22
 
17
23
  ```
18
24
  gem build rubyfocus.gemspec
19
- gem install rubyfocus-0.1.0.gem
25
+ gem install rubyfocus-0.3.0.gem
20
26
  ```
21
27
 
22
28
  # Usage
@@ -64,13 +70,13 @@ d.projects.first.tasks
64
70
 
65
71
  What if you want to select only certain projects? Sure, you can use standard `Array#find` or `Array#select` methods, but you can also make use of a hash of values:
66
72
 
67
- ```
73
+ ```ruby
68
74
  d.projects.select(name: "Sample project")
69
75
  ```
70
76
 
71
77
  Once you have your objects, you can query them for more information:
72
78
 
73
- ```
79
+ ```ruby
74
80
  t = d.tasks.first
75
81
  t.name # => "Sample task"
76
82
  t.project.name # => "Sample project"
@@ -102,11 +108,25 @@ Rubyfocus makes use of this by fetching and reading OmniFocus' local store on yo
102
108
 
103
109
  Other goals include:
104
110
 
105
- * Updating via the [Omni Sync Server](https://manage.sync.omnigroup.com/) rather than relying on local files.
106
- * A couple of example projects using rubyfocus
111
+ * Registering with the Omni Sync Server, so you don't need to always delete + reinstantiate the database.
112
+ * Determining when you're "detached" from the latest version on the OSS.
113
+ * A couple of example projects using rubyfocus (especially a static webpage generator for kanban)
107
114
 
108
115
  # History
109
116
 
117
+ ## 0.4.0 // 2016-01-01
118
+
119
+ * Happy new year!
120
+ * [Modified] Container IDRef is now located on RankedItem, rather than having several on each RankedItem subclass.
121
+ * [New] RankedItems can look at their ancestry much more easily, using RankedItem#ancestry and RankedItem#contained_within?
122
+ * [New] Documents now forbid elements with duplicate IDs unless Document#allow_duplicate_ids is set to true.
123
+ * [New] Patchers now treate CREATE nodes on elements whose IDs already exist in the database as UPDATE nodes
124
+ * [Fixed] Patchers will now interpret missing parameters as "default values" e.g. project update without `status` parameter assumed to be active.
125
+
126
+ ## 0.3.1 // 2015-12-31
127
+
128
+ * [Bugfix] IDRefs will now return +nil+ if the relevant ID is not set.
129
+
110
130
  ## 0.3.0 // 2015-10-17
111
131
 
112
132
  * [New] Now supports remote syncing with the Omni Sync Server!
@@ -53,7 +53,7 @@ class Rubyfocus::Document
53
53
  end
54
54
 
55
55
  # Initialize with a URL, for remote fetching.
56
- # Not implemented yet
56
+ # Not implemented yet TODO implement
57
57
  def self.from_url(url)
58
58
  raise RuntimeError, "Rubyfocus::Document.from_url not yet implemented."
59
59
  # new(Rubyfocus::RemoteFetcher.new(url))
@@ -96,9 +96,22 @@ class Rubyfocus::Document
96
96
  end
97
97
  private :ivar_for
98
98
 
99
- # Add an element. Element should be a Project, Task, Context, Folder, or Setting
100
- # We assume whoever does this will set document appropriately on the element
101
- def add_element(e)
99
+ # Add an element. Element should be a Project, Task, Context, Folder, or Setting.
100
+ # If overwrite set to false and ID already occurs in the document, throw an error.
101
+ # If ID is nil, throw an error.
102
+ def add_element(e, overwrite:false)
103
+ # Error check
104
+ raise(Rubyfocus::DocumentElementException, "Adding element to document, but it has no ID.") if e.id.nil?
105
+ raise(Rubyfocus::DocumentElementException, "Adding element to document, but element with this ID already exists.") if !overwrite && has_id?(e.id)
106
+
107
+ # Otherwise, full steam ahead
108
+ e.document = self
109
+
110
+ if (dupe_element = self[e.id]) && overwrite
111
+ remove_element(dupe_element)
112
+ end
113
+
114
+ # Add to the correct array
102
115
  dest = ivar_for(e)
103
116
  if dest
104
117
  dest << e
@@ -108,12 +121,12 @@ class Rubyfocus::Document
108
121
  end
109
122
 
110
123
  # Remove an element from the document.
111
- # We assume whoever does this is smart enough to also set the element's #document value
112
- # to nil
113
124
  def remove_element(e)
114
125
  e = self[e] if e.is_a?(String)
115
126
  return if e.nil?
116
127
 
128
+ e.document = nil
129
+
117
130
  dest = ivar_for(e)
118
131
  if dest
119
132
  dest.delete(e)
@@ -131,16 +144,26 @@ class Rubyfocus::Document
131
144
  # For Searchable include
132
145
  alias_method :array, :elements
133
146
 
147
+
134
148
  #-------------------------------------------------------------------------------
135
149
  # Find elements from id
136
150
  def [] search_id
137
151
  self.elements.find{ |elem| elem.id == search_id }
138
152
  end
139
153
 
154
+ # Check if the document has an element of a given ID
155
+ def has_id?(id)
156
+ self.elements.any?{ |e| e.id == id }
157
+ end
158
+
140
159
  #---------------------------------------
141
160
  # YAML export
142
161
 
143
162
  def save(file)
144
163
  File.open(file, "w"){ |io| io.puts YAML::dump(self) }
145
164
  end
146
- end
165
+ end
166
+
167
+ #-------------------------------------------------------------------------------
168
+ # Exceptions
169
+ class Rubyfocus::DocumentElementException < Exception; end
@@ -6,7 +6,7 @@ module Rubyfocus
6
6
  name_id = "#{name}_id".to_sym
7
7
  attr_accessor name_id
8
8
  define_method(name) do
9
- return document && document.find(send(name_id))
9
+ return document && (id_value = send(name_id)) && document.find(id_value)
10
10
  end
11
11
 
12
12
  define_method("#{name}=") do |o|
@@ -4,7 +4,6 @@ class Rubyfocus::Context < Rubyfocus::RankedItem
4
4
  return (node.name == "context")
5
5
  end
6
6
 
7
- idref :container
8
7
  attr_accessor :location
9
8
 
10
9
  def apply_xml(n)
@@ -4,8 +4,6 @@ class Rubyfocus::Folder < Rubyfocus::RankedItem
4
4
  return (node.name == "folder")
5
5
  end
6
6
 
7
- idref :container
8
-
9
7
  def apply_xml(n)
10
8
  super(n)
11
9
  conditional_set(:container_id, n.at_xpath("xmlns:folder")){ |e| e["idref"] }
@@ -13,8 +13,6 @@ class Rubyfocus::Item
13
13
  attr_accessor :id, :added, :modified, :document
14
14
 
15
15
  def initialize(document=nil, n=nil)
16
- self.document = document
17
-
18
16
  case n
19
17
  when Nokogiri::XML::Element
20
18
  apply_xml(n)
@@ -24,6 +22,8 @@ class Rubyfocus::Item
24
22
  send(setter,v) if respond_to?(setter)
25
23
  end
26
24
  end
25
+
26
+ document.add_element(self) if document
27
27
  end
28
28
 
29
29
  def apply_xml(n)
@@ -45,14 +45,6 @@ class Rubyfocus::Item
45
45
  inspect_properties.each_with_object({}){ |s,hsh| hsh[s] = self.send(s) }
46
46
  end
47
47
 
48
- #---------------------------------------
49
- # Document set/get methods
50
- def document= d
51
- @document.remove_element(self) if @document
52
- @document = d
53
- @document.add_element(self) if @document
54
- end
55
-
56
48
  #-------------------------------------------------------------------------------
57
49
  # Private inspect methods
58
50
 
@@ -1,6 +1,25 @@
1
1
  class Rubyfocus::RankedItem < Rubyfocus::NamedItem
2
2
  attr_accessor :rank
3
3
 
4
+ # Ranked items also happen to be contained items
5
+ idref :container
6
+
7
+ # Retrieve a full list of the parents of this item. [0] = immediate parent
8
+ def ancestry
9
+ if container
10
+ [container] + container.ancestry
11
+ else
12
+ []
13
+ end
14
+ end
15
+
16
+ # Is this item contained within another? You may supply an object, string or integer ID, hash of properties,
17
+ # or proc to run on each item.
18
+ def contained_within?(object)
19
+ object = document.find(object) if [String, Fixnum, Hash, Proc].include?(object.class)
20
+ ancestry.include?(object)
21
+ end
22
+
4
23
  def apply_xml(n)
5
24
  super(n)
6
25
  conditional_set(:rank, n.at_xpath("xmlns:rank")){ |e| e.inner_html.to_i }
@@ -15,7 +15,7 @@ class Rubyfocus::Task < Rubyfocus::RankedItem
15
15
  # * document
16
16
 
17
17
  attr_accessor :note, :flagged, :order, :start, :due, :completed
18
- idref :container, :context
18
+ idref :context
19
19
 
20
20
  def initialize(document, n=nil)
21
21
  @order = :sequential
@@ -92,45 +92,32 @@ class Rubyfocus::Patch
92
92
  end
93
93
  end
94
94
 
95
- # Apply this patch to a document. Who needs error checking amirite?
95
+ # Apply this patch to a document.
96
96
  def apply_to!(document)
97
97
  # Updates modify elements
98
- self.update.each do |node|
99
- elem = document[node["id"]]
100
-
101
- # Tasks can become projects and v.v.: check this.
102
- if [Rubyfocus::Task, Rubyfocus::Project].include?(elem.class)
103
- should_be_project = (node.at_xpath("xmlns:project") != nil)
104
- if (elem.class == Rubyfocus::Project) && !should_be_project
105
- elem.document = nil # Remove this from current document
106
- elem = elem.to_task # Convert to task
107
- elem.document = document # Insert again!
108
- elsif (elem.class == Rubyfocus::Task) && should_be_project
109
- elem.document = nil # Remove this from current document
110
- elem = elem.to_project # Convert to task
111
- elem.document = document # Insert again!
112
- end
113
- end
114
-
115
- elem.apply_xml(node) if elem
116
- end
117
-
98
+ self.update.each{ |node| update_node(document, node) }
99
+
118
100
  # Deletes remove elements
119
- self.delete.each do |node|
120
- document.remove_element(node["id"])
121
- end
101
+ self.delete.each{ |node| document.remove_element(node["id"]) }
122
102
 
123
103
  # Creates make new elements
124
- self.create.each do |node|
125
- if Rubyfocus::Parser.parse(document, node).nil?
126
- raise RuntimeError, "Encountered unparsable XML during patch reading: #{node}."
127
- end
128
- end
104
+ self.create.each{ |node| update_node(document, node) }
129
105
 
130
106
  # Modify current patch_id to show new value
131
107
  document.patch_id = self.to_id
132
108
  end
133
109
 
110
+ # Atomic node update code
111
+ def update_node(document, node)
112
+ # Create new element with correct ID. Then add to document, overwriting previous element(s)
113
+ new_node = Rubyfocus::Parser.parse(nil, node)
114
+ if new_node
115
+ document.add_element(new_node, overwrite: true)
116
+ else
117
+ raise(RuntimeError, "Encountered unparsable XML during patch reading: #{node}.")
118
+ end
119
+ end
120
+
134
121
  # String representation
135
122
  def to_s
136
123
  if from_ids.size == 1
data/version.txt CHANGED
@@ -1 +1 @@
1
- 0.3.1
1
+ 0.4.0
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyfocus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan-Yves Ruzicka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-29 00:00:00.000000000 Z
11
+ date: 2016-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri