rubyfocus 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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