rtasklib 0.1.4 → 0.1.5

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: 6594a20f1c965ccf953918bf9fb7eba89832dc70
4
- data.tar.gz: 6c889440f54c76d7eac9f46e62ad1d7300e44399
3
+ metadata.gz: 6dd45dd292c87269556e732b902f18cf38a098eb
4
+ data.tar.gz: a8adeee47b89eb6cadc2a5cf79e4825711f4e181
5
5
  SHA512:
6
- metadata.gz: 23c9ecccf416dcf6e94835258096ec96dc5257d9ee1109a31014c11100bfd329b11b02ca42a59d2c72c095b6f360b21ddbdc5db1726cdf0c21dc61a6fb127b5b
7
- data.tar.gz: b9962b934cca761f7a8e3c7ad623d76238a1b10722e0e1eb876c75d06640cf5b326e9339f31da34cfe73c12669f7b322d7e1f2652eef35afaf0c1782380aa670
6
+ metadata.gz: f2ad056508be0fabd7365c5431abe144220283ea6ba7beb77718d807f1549307876f1cde5708342339a50b3c4e6fa9b0ce4d9781b52c670020d55397850d5678
7
+ data.tar.gz: 8afd466375b305d8ed56fe5e98b66328391063c864db40637a02ecb9f65b58bfc4b6f49542977a739a7f44ccd394744deb9e0d5a7fe7bd2e3ff7b028eb2459c1
data/.gitignore CHANGED
@@ -6,7 +6,6 @@
6
6
  /pkg/
7
7
  /doc/
8
8
  /spec/reports/
9
+ /spec/data/
9
10
  /tmp/
10
11
  *.py
11
- lib/rtasklib/marshallable.rb
12
- spec/marshallable_spec.rb
data/.travis.yml CHANGED
@@ -25,6 +25,8 @@ before_install:
25
25
  - export TASKRC=/home/travis/build/dropofwill/rtasklib/spec/data/.taskrc
26
26
  - export TASKDATA=/home/travis/build/dropofwill/rtasklib/spec/data/.task
27
27
  - task _version
28
+ - task export
29
+ - task add "Test task persistence"
28
30
 
29
31
  script: 'bundle exec rake'
30
32
 
data/README.md CHANGED
@@ -36,7 +36,18 @@ Or install it yourself as:
36
36
 
37
37
  ## Usage
38
38
 
39
- TODO: Write usage instructions here
39
+ ```
40
+ require 'rtasklib'
41
+
42
+ tw = Rtasklib::TW.new('../path/to/.task')
43
+
44
+ # do some stuff with the task database
45
+ # available commands are documented in the Controller class
46
+
47
+ tw.all
48
+ ```
49
+
50
+ [Controller docs](http://will-paul.com/rtasklib/Rtasklib/Controller.html)
40
51
 
41
52
 
42
53
  ## Development
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
+ require "open3"
4
+ require "pp"
3
5
 
4
6
  # run tests with `rake spec`
5
7
  RSpec::Core::RakeTask.new :spec do |task|
@@ -10,9 +12,12 @@ task default: :spec
10
12
 
11
13
  desc "Update and publish docs to gh-pages"
12
14
  task :docs do |task|
13
- `yard docs`
14
- `ghp-import -p doc`
15
- end
15
+ o, s = Open3.capture2("yard doc")
16
+ o.split("\n").each { |line| p line }
17
+ p ""
16
18
 
17
- task :build_rpm do
19
+ Open3.capture2("ghp-import -p doc")
18
20
  end
21
+
22
+ # task :build_rpm do
23
+ # end
data/lib/rtasklib.rb CHANGED
@@ -2,15 +2,15 @@ require_relative "rtasklib/version"
2
2
  require_relative "rtasklib/models"
3
3
  require_relative "rtasklib/execute"
4
4
  require_relative "rtasklib/controller"
5
+ require_relative "rtasklib/helpers"
5
6
  require_relative "rtasklib/taskrc"
6
7
 
7
- require "open3"
8
8
  require "pathname"
9
9
 
10
10
  module Rtasklib
11
11
 
12
12
  class TaskWarrior
13
- attr_reader :version, :data_location, :taskrc,
13
+ attr_reader :version, :data_location, :taskrc, :udas,
14
14
  :override, :override_a, :override_str
15
15
 
16
16
  include Controller
@@ -25,22 +25,24 @@ module Rtasklib
25
25
  LOWEST_VERSION = Gem::Version.new('2.4.0')
26
26
 
27
27
  def initialize data="#{Dir.home}/.task", opts = {}
28
+ # Check TaskWarrior version, and throw warning if unavailable
29
+ begin
30
+ @version = check_version
31
+ rescue
32
+ warn "Couldn't verify TaskWarrior's version"
33
+ end
28
34
 
29
35
  @data_location = data
30
36
  override_h = DEFAULTS.merge({data_location: data}).merge(opts)
31
37
  @override = Taskrc.new(override_h, :hash)
32
38
  @override_a = override.model_to_rc
33
39
  @taskrc = get_rc
34
-
35
- # Check TaskWarrior version, and throw warning if unavailable
36
- begin
37
- @version = check_version(get_version())
38
- rescue
39
- warn "Couldn't verify TaskWarrior's version"
40
- end
40
+ @udas = get_udas
41
+ add_udas_to_model!(udas) unless udas.nil?
41
42
  end
42
43
 
43
- def check_version version
44
+ def check_version version=nil
45
+ version = get_version if version.nil?
44
46
  if version < LOWEST_VERSION
45
47
  warn "The current TaskWarrior version, #{version}, is untested"
46
48
  end
@@ -3,6 +3,15 @@ require "oj"
3
3
 
4
4
  module Rtasklib
5
5
 
6
+ # Accessed through the main TW, which includes this module, e.g. `tw.all`
7
+ #
8
+ # Ideally should only be the well documented public, user-facing methods.
9
+ # We're getting there.
10
+ #
11
+ # By convention bang methods modify the task database, and non-bang read
12
+ # from the database, e.g. `Controller#all` vs `Controller#modify!`
13
+ #
14
+ # XXX: depends on @override_a currently, which isn't great.
6
15
  module Controller
7
16
  extend self
8
17
 
@@ -12,65 +21,115 @@ module Rtasklib
12
21
  # @api public
13
22
  def all
14
23
  all = []
15
- Execute.task_popen3(*@override_a, "export") do |i, o, e, t|
24
+ Execute.task_popen3(*override_a, "export") do |i, o, e, t|
16
25
  all = MultiJson.load(o.read).map do |x|
17
26
  Rtasklib::Models::TaskModel.new(x)
18
27
  end
19
28
  end
20
- all
29
+ return all
21
30
  end
22
31
 
23
- def filter ids: nil, tags: nil, query: nil
32
+ # Retrieves the current task list filtered by id, tag, or a dom query
33
+ #
34
+ # @example filter by an array of ids
35
+ # tw.some(ids: [1..2, 5])
36
+ # @example filter by tags
37
+ # tw.some(tags: ["+school", "or", "-work"]
38
+ #
39
+ # @param ids [Array<Range, Fixnum, String>, String, Range, Fixnum]
40
+ # @param tags [Array<String>, String]
41
+ # @param dom [Array<String>, String]
42
+ # @return [Array<Models::TaskModel>]
43
+ # @api public
44
+ def some ids: nil, tags: nil, dom: nil
45
+ some = []
46
+ filter_s = Helpers.filter(ids: ids, tags: tags, dom: dom)
47
+ Execute.task_popen3(*@override_a, filter_s, "export") do |i, o, e, t|
48
+ some = MultiJson.load(o.read).map do |x|
49
+ Rtasklib::Models::TaskModel.new(x)
50
+ end
51
+ end
52
+ return some
24
53
  end
25
54
 
26
- def add!
55
+ #
56
+ #
57
+ # @param ids [Array<Range, Fixnum, String>, String, Range, Fixnum]
58
+ # @param tags [Array<String>, String]
59
+ # @param dom [Array<String>, String]
60
+ # @api public
61
+ def add! description
27
62
  end
28
63
 
29
- # def modify! attr:, val:, ids: nil, tags: nil, query: nil
30
- # end
64
+ #
65
+ #
66
+ # @param ids [Array<Range, Fixnum, String>, String, Range, Fixnum]
67
+ # @param tags [Array<String>, String]
68
+ # @param dom [Array<String>, String]
69
+ # @api public
70
+ def modify! attr:, val:, ids: nil, tags: nil, dom: nil
71
+ f = filter(ids, tags, dom)
72
+ query = "#{f} modify #{attr} #{val}"
73
+ Execute.task_popen3(*override_a, query) do |i, o, e, t|
74
+ return t.value
75
+ end
76
+ end
31
77
 
78
+ # Directly call `task undo`, which only applies to edits to the task db
79
+ # not configuration changes
80
+ #
81
+ # @api public
32
82
  def undo!
83
+ Execute.task_popen3(*override_a, "undo") do |i, o, e, t|
84
+ return t.value
85
+ end
33
86
  end
34
87
 
88
+ # Update a configuration variable in the .taskrc
89
+ #
90
+ # @param attr [String]
91
+ # @param val [String]
92
+ # @api public
35
93
  def update_config! attr, val
36
94
  Execute.task_popen3(*override_a, "config #{attr} #{val}") do |i, o, e, t|
37
95
  return t.value
38
96
  end
39
97
  end
40
98
 
41
- # Retrieves an array of hashes with info about the udas currently available
99
+ # Retrieves a hash of hashes with info about the UDAs currently available
100
+ #
101
+ # @return [Hash{Symbol=>Hash}]
102
+ # @api public
42
103
  def get_udas
104
+ udas = {}
43
105
  taskrc.config.attributes
44
- .select { |attr, val| uda_attr? attr }
106
+ .select { |attr, val| Helpers.uda_attr? attr }
45
107
  .sort
46
- .chunk { |attr, val| arbitrary_attr attr }
47
- .map do |attr, arr|
108
+ .chunk { |attr, val| Helpers.arbitrary_attr attr }
109
+ .each do |attr, arr|
48
110
  uda = arr.map do |pair|
49
- key = deep_attr(pair[0])
50
- val = pair[1]
51
- [key, val]
111
+ [Helpers.deep_attr(pair[0]), pair[1]]
52
112
  end
53
- {attr.to_sym => Hash[uda]}
113
+ udas[attr.to_sym] = Hash[uda]
54
114
  end
115
+ return udas
55
116
  end
56
117
 
57
- # Is a given attribute dealing with udas?
58
- def uda_attr? attr
59
- attr.to_s.start_with? "uda"
60
- end
61
- private :uda_attr?
62
-
63
- # Returns part of attribute at a given depth
64
- def arbitrary_attr attr, depth: 1
65
- attr.to_s.split("_")[depth]
66
- end
67
-
68
- # Returns all attribute string after given depth
69
- def deep_attr attr, depth: 2
70
- attr.to_s.split("_")[depth..-1].join("_")
118
+ # Add new found udas to our internal TaskModel
119
+ #
120
+ # @param uda_hash [Hash{Symbol=>Hash}]
121
+ # @param type [Class, nil]
122
+ # @param model [Models::TaskModel, Class]
123
+ # @api protected
124
+ def add_udas_to_model! uda_hash, type=nil, model=Models::TaskModel
125
+ uda_hash.each do |attr, val|
126
+ val.each do |k, v|
127
+ type = Helpers.determine_type(v) if type.nil?
128
+ model.attribute attr, type
129
+ end
130
+ end
71
131
  end
72
- private :deep_attr
73
-
132
+ protected :add_udas_to_model!
74
133
 
75
134
  # Retrieve an array of the uda names
76
135
  #
@@ -95,18 +154,32 @@ module Rtasklib
95
154
  end
96
155
  end
97
156
 
98
- def create_uda name, type: "string", label: nil, values: nil,
157
+ # Add a UDA to the users config/database
158
+ #
159
+ # @param name [String]
160
+ # @param type [String]
161
+ # @param label [String]
162
+ # @param values [String]
163
+ # @param default [String]
164
+ # @param urgency [String]
165
+ # @return [Boolean] success
166
+ # @api public
167
+ def create_uda! name, type: "string", label: nil, values: nil,
99
168
  default: nil, urgency: nil
100
169
  label = name if label.nil?
101
- p name, label, values, default, urgency
102
170
 
103
- update_config "uda.#{name}.type", type
104
- update_config "uda.#{name}.label", label
105
- update_config "uda.#{name}.values", values unless values.nil?
106
- update_config "uda.#{name}.default", default unless default.nil?
107
- update_config "uda.#{name}.urgency", urgency unless urgency.nil?
171
+ update_config("uda.#{name}.type", type)
172
+ update_config("uda.#{name}.label", label)
173
+ update_config("uda.#{name}.values", values) unless values.nil?
174
+ update_config("uda.#{name}.default", default) unless default.nil?
175
+ update_config("uda.#{name}.urgency", urgency) unless urgency.nil?
108
176
  end
109
177
 
178
+ # Calls `task _show` with initial overrides returns a Taskrc object of the
179
+ # result
180
+ #
181
+ # @return [Taskrc]
182
+ # @api public
110
183
  def get_rc
111
184
  res = []
112
185
  Execute.task_popen3(*@override_a, "_show") do |i, o, e, t|
@@ -115,22 +188,16 @@ module Rtasklib
115
188
  Taskrc.new(res, :array)
116
189
  end
117
190
 
191
+ # Calls `task _version` and returns the result
192
+ #
193
+ # @return [String]
194
+ # @api public
118
195
  def get_version
119
196
  version = nil
120
- Execute.task_popen3(*@override_a, "_version") do |i, o, e, t|
121
- version = to_gem_version(o.read.chomp)
197
+ Execute.task_popen3("_version") do |i, o, e, t|
198
+ version = Helpers.to_gem_version(o.read.chomp)
122
199
  end
123
200
  version
124
201
  end
125
-
126
- # Converts a string of format "1.6.2 (adf342jsd)" to Gem::Version object
127
- #
128
- #
129
- def to_gem_version raw
130
- std_ver = raw.chomp.gsub(' ','.').delete('(').delete(')')
131
- Gem::Version.new std_ver
132
- end
133
- private :to_gem_version
134
-
135
202
  end
136
203
  end
@@ -12,12 +12,23 @@ module Rtasklib
12
12
  so \s taskwarrior \s can \s proceed\? \s
13
13
  \(yes/no\)}x }
14
14
 
15
- # popen versions
15
+ # Use Open3#popen3 to execute a unix program with an array of options
16
+ # and an optional block to handle the response.
16
17
  #
18
+ # @example
19
+ # Execute.popen3("task", "export") do |i, o, e, t|
20
+ # # Arbitrary code to handle the response...
21
+ # end
22
+ #
23
+ # @param program [String]
24
+ # @param opts [Array<String>] args to pass directly to the program
25
+ # @param block [Block] to execute after thread is successful
26
+ # @yield [i,o,e,t] STDIN, STDOUT, STDERR, and the thread to that block.
27
+ # @api public
17
28
  def popen3 program='task', *opts, &block
18
29
  execute = opts.unshift(program)
19
30
  execute = execute.join(" ")
20
- p execute
31
+ warn execute
21
32
 
22
33
  Open3.popen3(execute) do |i, o, e, t|
23
34
  handle_response(e, t)
@@ -25,10 +36,30 @@ module Rtasklib
25
36
  end
26
37
  end
27
38
 
39
+ # Same as Execute#popen3, only defaults to using the 'task' program for
40
+ # convenience.
41
+ #
42
+ # @example
43
+ # Execute.task_popen3("export") do |i, o, e, t|
44
+ # # Arbitrary code to handle the response...
45
+ # end
46
+ #
47
+ # @param opts [Array<String>] args to pass directly to the program
48
+ # @param block [Block] to execute after thread is successful
49
+ # @yield [i,o,e,t] STDIN, STDOUT, STDERR, and the thread to that block.
50
+ # @api public
28
51
  def task_popen3 *opts, &block
29
52
  popen3('task', opts, &block)
30
53
  end
31
54
 
55
+ # Same as Execute#popen3, but yields each line of input
56
+ #
57
+ # @param program [String]
58
+ # @param opts [Array<String>] args to pass directly to the program
59
+ # @param block [Block] to execute after thread is successful
60
+ # @yield [l,i,o,e,t] a line of STDIN, STDIN, STDOUT, STDERR,
61
+ # and the thread to that block.
62
+ # @api public
32
63
  def each_popen3 program='task', *opts, &block
33
64
  popen3(program, *opts) do |i, o, e, t|
34
65
  o.each_line do |l|
@@ -37,49 +68,28 @@ module Rtasklib
37
68
  end
38
69
  end
39
70
 
71
+ # Same as Execute#each_popen3, but calls it with the 'task' program
72
+ #
73
+ # @param opts [Array<String>] args to pass directly to the program
74
+ # @param block [Block] to execute after thread is successful
75
+ # @yield [l,i,o,e,t] a line of STDIN, STDIN, STDOUT, STDERR,
76
+ # and the thread to that block.
77
+ # @api public
40
78
  def task_each_popen3 *opts, &block
41
- popen3(program, *opts) do |i, o, e, t|
42
- yield(i, o, e, t)
79
+ each_popen3("task", *opts) do |l, i, o, e, t|
80
+ yield(l, i, o, e, t)
43
81
  end
44
82
  end
45
83
 
84
+ # Default error handling called in every popen3 call. Only executes if
85
+ # thread had a failing exit code
86
+ #
87
+ # @raise [RuntimeError] if failing exit code
46
88
  def handle_response stderr, thread
47
89
  unless thread.value.success?
48
90
  puts stderr.read
49
- exit(-1)
91
+ raise thread.inspect
50
92
  end
51
93
  end
52
-
53
- # Non-greedy json object detection
54
- # if /\{.*\}/ =~ l
55
- # p l.chomp
56
- # res.push(l.chomp)
57
- # end
58
- # def task create_new, *opts, &block
59
- # exp_regex = @@exp_regex
60
- # retval = 0
61
- # res = nil
62
- # buff = ""
63
- #
64
- # run("task", *opts) do |exp, procedure|
65
- # res = procedure.any do
66
- # puts exp
67
- # expect exp_regex[:create_rc] do
68
- # if create_new
69
- # send "yes"
70
- # else
71
- # send "no"
72
- # end
73
- # end
74
- # block.call if block_given?
75
- # end
76
-
77
- # Filters should be a list of values
78
- # Ranges interpreted as ids
79
- # 1...5 : "1-5"
80
- # 1..5 : "1-4"
81
- # 1 : "1"
82
- # and joined with ","
83
- # [1...5, 8, 9] : "1-5,8,9"
84
94
  end
85
95
  end
@@ -0,0 +1,205 @@
1
+ require "multi_json"
2
+ require "oj"
3
+
4
+ module Rtasklib
5
+
6
+ # A collection of stateless, non-end-user facing functions available
7
+ # throughout the library
8
+ module Helpers
9
+ # make this module a stateless, singleton
10
+ extend self
11
+
12
+ # Converts ids, tags, and dom queries to a single string ready to pass
13
+ # directly to task.
14
+ #
15
+ # @param ids[Range, Array<String, Range, Fixnum>, String, Fixnum]
16
+ # @param tags[String, Array<String>]
17
+ # @param dom[String, Array<String>]
18
+ # @return [String] a string with ids tags and dom joined by a space
19
+ # @api public
20
+ def filter ids: nil, tags: nil, dom: nil
21
+ id_s = tag_s = dom_s = ""
22
+ id_s = process_ids(ids) unless ids.nil?
23
+ tag_s = process_tags(tags) unless tags.nil?
24
+ dom_s = process_dom(dom) unless dom.nil?
25
+ return "#{id_s} #{tag_s} #{dom_s}".strip
26
+ end
27
+
28
+ # Filters should be a list of values
29
+ # Ranges interpreted as ids
30
+ # 1...5 : "1,2,3,4,5"
31
+ # 1..5 : "1,2,3,4"
32
+ # 1 : "1"
33
+ # and joined with ","
34
+ # [1...5, 8, 9] : "1,2,3,4,5,8,9"
35
+ #
36
+ # @param id_a [Array<String, Range, Fixnum>]
37
+ # @return [String]
38
+ # @api public
39
+ def id_a_to_s id_a
40
+ id_a.map do |el|
41
+ proc_ids = process_ids(el)
42
+ proc_ids
43
+ end.compact.join(",")
44
+ end
45
+
46
+ # Converts arbitrary id input to a task safe string
47
+ #
48
+ # @param ids[Range, Array<String, Range, Fixnum>, String, Fixnum]
49
+ # @api public
50
+ def process_ids ids
51
+ case ids
52
+ when Range
53
+ return id_range_to_s(ids)
54
+ when Array
55
+ return id_a_to_s(ids)
56
+ when String
57
+ return ids.delete(" ")
58
+ when Fixnum
59
+ return ids
60
+ end
61
+ end
62
+
63
+ # Convert a range to a comma separated strings, e.g. 1..4 -> "1,2,3,4"
64
+ #
65
+ # @param id_range [Range]
66
+ # @return [Array<String>]
67
+ # @api public
68
+ def id_range_to_s id_range
69
+ id_range.to_a.join(",")
70
+ end
71
+
72
+ # Convert a tag string or an array of strings to a space separated string
73
+ #
74
+ # @param tags [String, Array<String>]
75
+ # @api private
76
+ def process_tags tags
77
+ case tags
78
+ when String
79
+ tags.split(" ").map { |t| process_tag t }.join(" ")
80
+ when Array
81
+ tags.map { |t| process_tags t }.join(" ")
82
+ end
83
+ end
84
+
85
+ # Ensures that a tag begins with a + or -
86
+ #
87
+ # @return [String]
88
+ # @api public
89
+ def process_tag tag
90
+ reserved_symbols = %w{+ - and or xor < <= = != >= > ( )}
91
+
92
+ # convert plain tags to plus tags
93
+ unless tag.start_with?(*reserved_symbols)
94
+ tag = "+#{tag}"
95
+ end
96
+ return tag
97
+ end
98
+
99
+ # @api private
100
+ def process_dom dom
101
+ end
102
+
103
+ # Is a given taskrc attribute dealing with udas?
104
+ #
105
+ # @api public
106
+ def uda_attr? attr
107
+ attr.to_s.start_with? "uda"
108
+ end
109
+
110
+ # Returns part of attribute at a given depth
111
+ #
112
+ # @api public
113
+ def arbitrary_attr attr, depth: 1
114
+ attr.to_s.split("_")[depth]
115
+ end
116
+
117
+ # Returns all attribute string after given depth
118
+ #
119
+ # @api public
120
+ def deep_attr attr, depth: 2
121
+ attr.to_s.split("_")[depth..-1].join("_")
122
+ end
123
+
124
+ # Converts a string of format "1.6.2 (adf342jsd)" to Gem::Version object
125
+ #
126
+ # @param raw [String]
127
+ # @return [Gem::Version]
128
+ # @api public
129
+ def to_gem_version raw
130
+ std_ver = raw.chomp.gsub(' ','.').delete('(').delete(')')
131
+ Gem::Version.new std_ver
132
+ end
133
+
134
+ # Determine the type that a value should be coerced to
135
+ # Int needs to precede float because ints are also floats
136
+ # Doesn't detect arrays, b/c task stores these as comma separated strings
137
+ # which could just as easily be Strings....
138
+ # If nothing works it defaults to String.
139
+ # TODO: JSON parse
140
+ #
141
+ # @param value [Object] anything that needs to be coerced, probably string
142
+ # @return [Axiom::Types::Boolean, Integer, Float, String]
143
+ # @api public
144
+ def determine_type value
145
+ if boolean? value
146
+ return Axiom::Types::Boolean
147
+ elsif integer? value
148
+ return Integer
149
+ elsif float? value
150
+ return Float
151
+ elsif json? value
152
+ return MultiJson
153
+ else
154
+ return String
155
+ end
156
+ end
157
+
158
+ # Can the input be coerced to an integer without losing information?
159
+ #
160
+ # @return [Boolean] true if coercible, false if not
161
+ # @api private
162
+ def integer? value
163
+ value.to_i.to_s == value rescue false
164
+ end
165
+ private :integer?
166
+
167
+ # Can the input be coerced to a JSON object without losing information?
168
+ #
169
+ # @return [Boolean] true if coercible, false if not
170
+ # @api private
171
+ def json? value
172
+ begin
173
+ return false unless value.is_a? String
174
+ MultiJson.load(value)
175
+ true
176
+ rescue MultiJson::ParseError
177
+ false
178
+ end
179
+ end
180
+ private :json?
181
+
182
+ # Can the input be coerced to a float without losing information?
183
+ #
184
+ # @return [Boolean] true if coercible, false if not
185
+ # @api private
186
+ def float? value
187
+ begin
188
+ true if Float(value)
189
+ rescue
190
+ false
191
+ end
192
+ end
193
+ private :float?
194
+
195
+ # Can the input be coerced to a boolean without losing information?
196
+ #
197
+ # @return [Boolean] true if coercible, false if not
198
+ # @api private
199
+ def boolean? value
200
+ ["on", "off", "yes", "no", "false", "true"]
201
+ .include? value.to_s.downcase rescue false
202
+ end
203
+ private :boolean?
204
+ end
205
+ end
@@ -1,22 +1,37 @@
1
1
  require "virtus"
2
2
  require "active_model"
3
+ require 'iso8601'
4
+ require 'date'
3
5
 
4
6
  module Rtasklib::Models
5
- ValidationError = Class.new RuntimeError
6
7
 
7
- class UUID < Virtus::Attribute
8
- def coerce(value)
9
- value.to_s
8
+ # A subclass of the ISO8601::Duration object that use `task calc` to parse
9
+ # string names like 'weekly', 'biannual', and '3 quarters'
10
+ #
11
+ # Modifies the #initialize method, preserving the original string duration
12
+ class TWDuration < ISO8601::Duration
13
+ attr_reader :frozen_value
14
+
15
+ def initialize input, base=nil
16
+ @frozen_value = input.dup.freeze
17
+ parsed = `task calc #{input}`.chomp
18
+
19
+ super parsed, base
20
+ end
21
+ end
22
+
23
+ # Custom coercer to change a string input into an TWDuration object
24
+ class VirtusDuration < Virtus::Attribute
25
+ def coerce(v)
26
+ if v.nil? || v.blank? then nil else TWDuration.new(v) end
10
27
  end
11
28
  end
12
29
 
13
30
  RcBooleans = Virtus.model do |mod|
14
31
  mod.coerce = true
15
32
  mod.coercer.config.string.boolean_map = {
16
- 'no' => false,
17
- 'yes' => true,
18
- 'on' => true,
19
- 'off' => false }
33
+ 'yes' => true, 'on' => true,
34
+ 'no' => false, 'off' => false }
20
35
  end
21
36
 
22
37
  class TaskrcModel
@@ -61,7 +76,7 @@ module Rtasklib::Models
61
76
  attribute :wait, DateTime
62
77
 
63
78
  # Required only for tasks that are Recurring or have Recurring Parent
64
- attribute :recur, DateTime
79
+ attribute :recur, VirtusDuration
65
80
 
66
81
  # Optional except for tasks with Recurring Parents
67
82
  attribute :due, DateTime
@@ -74,8 +89,6 @@ module Rtasklib::Models
74
89
  attribute :imask, String
75
90
  attribute :modified, DateTime
76
91
 
77
- # TODO: handle arbitrary UDA's
78
-
79
92
  # Refactoring idea, need to understand Virtus internals a bit better
80
93
  # [:mask, :imask, :modified, :status, :uuid, :entry].each do |ro_attr|
81
94
  # define_method("set_#{ro_attr.to_s}") do |value|
@@ -25,7 +25,7 @@ module Rtasklib
25
25
  hash_to_model(rc)
26
26
  when :path
27
27
  if path_exist?(rc)
28
- mappable_to_model(File.open(rc))
28
+ mappable_to_model(File.open(rc).readlines)
29
29
  else
30
30
  raise RuntimeError.new("rc path does not exist on the file system")
31
31
  end
@@ -53,7 +53,7 @@ module Rtasklib
53
53
  # Converts a .taskrc file path into a Hash that can be converted into a
54
54
  # TaskrcModel object
55
55
  #
56
- # @param rc_path [String,Pathname] a valid pathname to a .taskrc file
56
+ # @param rc_file [String,Pathname] a valid pathname to a .taskrc file
57
57
  # @return [Models::TaskrcModel] the instance variable config
58
58
  # @api private
59
59
  def mappable_to_model rc_file
@@ -123,7 +123,6 @@ module Rtasklib
123
123
 
124
124
  # Dynamically add a Virtus attr, detect Boolean, Integer, and Float types
125
125
  # based on the value, otherwise just treat it like a string.
126
- # Int needs to precede float because ints are also floats
127
126
  # Modifies the config instance variable
128
127
  # TODO: May also be able to detect arrays
129
128
  #
@@ -132,22 +131,14 @@ module Rtasklib
132
131
  # @return [undefined]
133
132
  # @api private
134
133
  def add_model_attr attr, value
135
- if boolean? value
136
- config.attribute attr.to_sym, Axiom::Types::Boolean
137
- elsif integer? value
138
- config.attribute attr.to_sym, Integer
139
- elsif float? value
140
- config.attribute attr.to_sym, Float
141
- else
142
- config.attribute attr.to_sym, String
143
- end
134
+ config.attribute(attr.to_sym, Helpers.determine_type(value))
144
135
  end
145
136
  private :add_model_attr
146
137
 
147
138
  # Modifies the value of a given attr in the config object
148
139
  #
149
140
  # @param attr [#to_s] the name for the attr, e.g. "json_array"
150
- # @param attr [String] the value of the attr, e.g. "yes"
141
+ # @param value [String] the value of the attr, e.g. "yes"
151
142
  # @return [undefined]
152
143
  # @api public
153
144
  def set_model_attr_value attr, value
@@ -194,20 +185,5 @@ module Rtasklib
194
185
  end
195
186
  end
196
187
  private :path_exist?
197
-
198
- def integer? value
199
- value.to_i.to_s == value
200
- end
201
- private :integer?
202
-
203
- def float? value
204
- Float(value) rescue false
205
- end
206
- private :float?
207
-
208
- def boolean? value
209
- ["on", "off", "yes", "no", "false", "true"].include? value.to_s.downcase
210
- end
211
- private :boolean?
212
188
  end
213
189
  end
@@ -1,3 +1,3 @@
1
1
  module Rtasklib
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5"
3
3
  end
@@ -3,16 +3,62 @@ require 'spec_helper'
3
3
  describe Rtasklib::Controller do
4
4
  include Rtasklib::Controller
5
5
 
6
- it 'can handle sub version numbers' do
7
- expect(to_gem_version("2.5.3 (afdj5)"))
8
- .to eq(Gem::Version.new("2.5.3.afdj5"))
6
+ shared_examples_for 'export' do
7
+ it 'should return an Array' do
8
+ expect(subject.class).to eq Array
9
+ end
10
+
11
+ it 'should return an Array<TaskModel>' do
12
+ expect(subject.each do |t|
13
+ expect(t.class).to eq(Rtasklib::Models::TaskModel)
14
+ end)
15
+ end
16
+ end
17
+
18
+ shared_examples_for 'export all' do
19
+ it_behaves_like 'export'
20
+
21
+ it 'should load in the correct number of task models' do
22
+ expect(subject.size).to eq(4)
23
+ end
9
24
  end
10
25
 
11
26
  describe 'Rtasklib::Controller#all' do
27
+ subject { Rtasklib::TaskWarrior.new("spec/data/.task").all }
28
+ it_behaves_like 'export all'
29
+ end
30
+
31
+ describe 'Rtasklib::Controller#some' do
32
+
33
+ describe '#some without arguments should behave like #all' do
34
+ subject { Rtasklib::TaskWarrior.new("spec/data/.task").some }
35
+ it_behaves_like 'export all'
36
+ end
37
+
38
+ describe '#some should accept filter parameters' do
39
+ subject { Rtasklib::TaskWarrior.new("spec/data/.task").some ids:[1,2] }
40
+ it_behaves_like 'export'
41
+
42
+ it 'should load in the correct number of task models' do
43
+ expect(subject.size).to eq(2)
44
+ end
45
+ end
12
46
  end
13
47
 
14
48
  describe 'Rtasklib::Controller#check_uda' do
15
49
  it 'should return true if a uda is found that matches the input' do
16
50
  end
17
51
  end
52
+
53
+ describe 'Rtasklib::Controller#all' do
54
+ end
55
+
56
+ describe 'Rtasklib::Controller#add!' do
57
+ end
58
+
59
+ describe 'Rtasklib::Controller#modify!' do
60
+ end
61
+
62
+ describe 'Rtasklib::Controller#undo!' do
63
+ end
18
64
  end
@@ -1,2 +1,5 @@
1
1
  {"description":"Wash dishes","entry":"20150316T174823Z","modified":"20150316T174823Z","status":"pending","uuid":"1ecac94d-b4d6-4c02-98f7-75e4de03006b"}
2
2
  {"description":"Clean room","entry":"20150316T174852Z","modified":"20150316T174852Z","status":"pending","uuid":"12d3176a-b6b1-42ab-90ad-6cd6acf8d6a1"}
3
+ {"description":"Test recur","entry":"20150512T200658Z","modified":"20150512T200658Z","status":"pending","uuid":"d203a656-f7ab-4707-8e38-abdced29acc4"}
4
+ {"description":"Test recur","due":"20160101T045959Z","entry":"20150512T200658Z","modified":"20150512T200658Z","recur":"weekly","status":"recurring","uuid":"d203a656-f7ab-4707-8e38-abdced29acc4"}
5
+ {"description":"biannual test","due":"20160101T045959Z","entry":"20150512T204148Z","modified":"20150512T204148Z","recur":"biannual","status":"recurring","uuid":"45fc918e-f106-45d8-96b6-37eaf61f2f4e"}
@@ -1,2 +1,4 @@
1
1
  [description:"Wash dishes" entry:"1426528103" modified:"1426528103" status:"pending" uuid:"1ecac94d-b4d6-4c02-98f7-75e4de03006b"]
2
2
  [description:"Clean room" entry:"1426528132" modified:"1426528132" status:"pending" uuid:"12d3176a-b6b1-42ab-90ad-6cd6acf8d6a1"]
3
+ [description:"Test recur" due:"1451624399" entry:"1431461218" modified:"1431461218" recur:"weekly" status:"recurring" uuid:"d203a656-f7ab-4707-8e38-abdced29acc4"]
4
+ [description:"biannual test" due:"1451624399" entry:"1431463308" modified:"1431463308" recur:"biannual" status:"recurring" uuid:"45fc918e-f106-45d8-96b6-37eaf61f2f4e"]
@@ -4,3 +4,13 @@ new [description:"Wash dishes" entry:"1426528103" modified:"1426528103" status:"
4
4
  time 1426528132
5
5
  new [description:"Clean room" entry:"1426528132" modified:"1426528132" status:"pending" uuid:"12d3176a-b6b1-42ab-90ad-6cd6acf8d6a1"]
6
6
  ---
7
+ time 1431461218
8
+ new [description:"Test recur" entry:"1431461218" modified:"1431461218" status:"pending" uuid:"d203a656-f7ab-4707-8e38-abdced29acc4"]
9
+ ---
10
+ time 1431461251
11
+ old [description:"Test recur" entry:"1431461218" modified:"1431461218" status:"pending" uuid:"d203a656-f7ab-4707-8e38-abdced29acc4"]
12
+ new [description:"Test recur" due:"1451624399" entry:"1431461218" modified:"1431461218" recur:"weekly" status:"recurring" uuid:"d203a656-f7ab-4707-8e38-abdced29acc4"]
13
+ ---
14
+ time 1431463308
15
+ new [description:"biannual test" due:"1451624399" entry:"1431463308" modified:"1431463308" recur:"biannual" status:"recurring" uuid:"45fc918e-f106-45d8-96b6-37eaf61f2f4e"]
16
+ ---
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rtasklib::Helpers do
4
+
5
+ describe 'Rtasklib::Helpers#to_gem_version' do
6
+
7
+ it 'can handle sub version numbers' do
8
+ expect(Rtasklib::Helpers.to_gem_version("2.5.3 (afdj5)"))
9
+ .to eq(Gem::Version.new("2.5.3.afdj5"))
10
+ end
11
+ end
12
+
13
+ describe 'Rtasklib::Helpers#determine_type' do
14
+
15
+ it 'considers "10" an integer' do
16
+ expect(Rtasklib::Helpers.determine_type("10")).to eq(Integer)
17
+ end
18
+
19
+ it 'considers "10.1" a float' do
20
+ expect(Rtasklib::Helpers.determine_type("10.1")).to eq(Float)
21
+ end
22
+
23
+ it 'considers "on" a boolean' do
24
+ expect(Rtasklib::Helpers.determine_type("on")).to eq(Axiom::Types::Boolean)
25
+ end
26
+
27
+ it 'considers "{"yolo":[1,2,3]}" a JSON object' do
28
+ test_json = '{"id":1,"description":"Anonymous Book",
29
+ "entry":"20150115T190114Z","modified":"20150115T190114Z",
30
+ "project":"Read","status":"pending","tags":["stuff"],
31
+ "uuid":"c483b58d-a3f2-4a2a-b944-8b41414309cb",
32
+ "urgency":"2.45753"}'
33
+ expect(Rtasklib::Helpers.determine_type(test_json)).to eq(MultiJson)
34
+ end
35
+
36
+ it 'considers "on off" a String' do
37
+ expect(Rtasklib::Helpers.determine_type("on ")).to eq(String)
38
+ end
39
+ end
40
+
41
+ describe 'Rtasklib::Helpers#filter' do
42
+
43
+ it 'treats arrays of ranges properly' do
44
+ expect(Rtasklib::Helpers.filter(ids: [1..3,5...6])).to eq("1,2,3,5")
45
+ end
46
+
47
+ it 'treats arrays of strings properly' do
48
+ expect(Rtasklib::Helpers.filter(ids: ["1,2", "5"])).to eq("1,2,5")
49
+ end
50
+
51
+ it 'treats arrays of ints properly' do
52
+ expect(Rtasklib::Helpers.filter(ids: [1,2,3,4,5])).to eq("1,2,3,4,5")
53
+ end
54
+
55
+ it 'treats arrays mixed objects properly' do
56
+ expect(Rtasklib::Helpers.filter(ids: [1,2,3,4,5, 10..20, "7,8"]))
57
+ .to eq("1,2,3,4,5,10,11,12,13,14,15,16,17,18,19,20,7,8")
58
+ end
59
+
60
+ it 'treats tag arrays properly' do
61
+ expect(Rtasklib::Helpers.filter(tags: ["(", "+stuff", "or", "-school", ")", "work"]))
62
+ .to eq("( +stuff or -school ) +work")
63
+ end
64
+
65
+ it 'treats tag strings properly' do
66
+ expect(Rtasklib::Helpers.filter(tags: "+stuff -school work"))
67
+ .to eq("+stuff -school +work")
68
+ end
69
+
70
+ it 'treats tag/id combos properly' do
71
+ expect(Rtasklib::Helpers.filter(ids: [1,2,4..5], tags: "+stuff -school work"))
72
+ .to eq("1,2,4,5 +stuff -school +work")
73
+ end
74
+
75
+ end
76
+ end
@@ -6,8 +6,8 @@ describe Rtasklib do
6
6
  end
7
7
 
8
8
  describe Rtasklib::TaskWarrior do
9
- describe "Rtasklib::TaskWarrior.new('spec/data/.taskrc')" do
10
- subject{ Rtasklib::TaskWarrior.new('spec/data/.taskrc') }
9
+ describe "Rtasklib::TaskWarrior.new('spec/data/.task')" do
10
+ subject{ Rtasklib::TaskWarrior.new('spec/data/.task') }
11
11
  it 'has a version number' do
12
12
  expect(subject.version.class).to eq Gem::Version
13
13
  end
@@ -16,8 +16,8 @@ describe Rtasklib do
16
16
  end
17
17
  end
18
18
 
19
- describe "Rtasklib::TaskWarrior.new('spec/data/.taskrc', {color: off})" do
20
- subject{ Rtasklib::TaskWarrior.new('spec/data/.taskrc', {verbose: 'on'}) }
19
+ describe "Rtasklib::TaskWarrior.new('spec/data/.task', {color: off})" do
20
+ subject{ Rtasklib::TaskWarrior.new('spec/data/.task', {verbose: 'on'}) }
21
21
  it 'updates the default configuration override' do
22
22
  end
23
23
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rtasklib
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Will Paul
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-05-12 00:00:00.000000000 Z
11
+ date: 2015-05-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: virtus
@@ -245,8 +245,8 @@ files:
245
245
  - lib/rtasklib.rb
246
246
  - lib/rtasklib/controller.rb
247
247
  - lib/rtasklib/execute.rb
248
+ - lib/rtasklib/helpers.rb
248
249
  - lib/rtasklib/models.rb
249
- - lib/rtasklib/serializer.rb
250
250
  - lib/rtasklib/taskrc.rb
251
251
  - lib/rtasklib/version.rb
252
252
  - rtasklib.gemspec
@@ -257,9 +257,9 @@ files:
257
257
  - spec/data/.task/undo.data
258
258
  - spec/data/.taskrc
259
259
  - spec/execute_spec.rb
260
+ - spec/helpers_spec.rb
260
261
  - spec/models_spec.rb
261
262
  - spec/rtasklib_spec.rb
262
- - spec/serializer_spec.rb
263
263
  - spec/spec_helper.rb
264
264
  - spec/taskrc_spec.rb
265
265
  homepage: http://github.com/dropofwill/rtasklib
@@ -283,7 +283,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
283
283
  requirements:
284
284
  - taskwarrior, >=2.4.0
285
285
  rubyforge_project:
286
- rubygems_version: 2.4.5
286
+ rubygems_version: 2.2.2
287
287
  signing_key:
288
288
  specification_version: 4
289
289
  summary: A Ruby wrapper around the TaskWarrior CLI
@@ -1,10 +0,0 @@
1
- require "active_model"
2
- require "active_model/serializer"
3
-
4
- module Rtasklib
5
-
6
- module Models
7
- class TaskSerializer < ActiveModel::Serializer
8
- end
9
- end
10
- end
File without changes