rtasklib 0.1.0 → 0.1.1.pre.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.gitignore +4 -1
- data/.ruby-version +1 -0
- data/.travis.yml +30 -1
- data/Guardfile +11 -0
- data/PLAN.md +204 -0
- data/README.md +23 -2
- data/Rakefile +1 -2
- data/bin/console +5 -5
- data/bin/env +12 -0
- data/bin/setup +0 -0
- data/lib/rtasklib/controller.rb +50 -0
- data/lib/rtasklib/execute.rb +89 -0
- data/lib/rtasklib/models.rb +86 -0
- data/lib/rtasklib/serializer.rb +10 -0
- data/lib/rtasklib/taskrc.rb +213 -0
- data/lib/rtasklib/version.rb +1 -1
- data/lib/rtasklib.rb +48 -2
- data/rtasklib-0.1.0.gem +0 -0
- data/rtasklib.gemspec +14 -4
- data/spec/controller_spec.rb +10 -0
- data/spec/data/.task/backlog.data +2 -0
- data/spec/data/.task/completed.data +0 -0
- data/spec/data/.task/pending.data +2 -0
- data/spec/data/.task/undo.data +6 -0
- data/spec/data/.taskrc +231 -0
- data/spec/execute_spec.rb +2 -0
- data/spec/models_spec.rb +34 -0
- data/spec/rtasklib_spec.rb +25 -0
- data/spec/serializer_spec.rb +0 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/taskrc_spec.rb +78 -0
- metadata +179 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbef69d8085f3de726027c8c93d277d1b5e95e6b
|
4
|
+
data.tar.gz: efc01dfeb85579ba8539ea68df1ccf216684ea10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca2de133e12b01df7a4403948b946b9e018447921ea384714fc3c1a6e824e33e2175179b324bb9210bef56817500ca05e0378dd3ba5d093c9986de310477b760
|
7
|
+
data.tar.gz: 5e487152aa6a0a98b6d615e5bca94d10123d3eee416da29f35e98eaa55e7f567d528a5c600ae958aa83d306c79195fe8fe36e6aaa8466c05fa44d4bf753e67be
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.3
|
data/.travis.yml
CHANGED
@@ -1,3 +1,32 @@
|
|
1
1
|
language: ruby
|
2
|
+
|
2
3
|
rvm:
|
3
|
-
|
4
|
+
- 2.1.3
|
5
|
+
|
6
|
+
env:
|
7
|
+
- TASKWARRIOR=v2.4.1
|
8
|
+
|
9
|
+
before_install:
|
10
|
+
- gem install bundler
|
11
|
+
- sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
12
|
+
- sudo apt-get update -qq
|
13
|
+
- sudo apt-get install -qq build-essential cmake uuid-dev g++-4.8
|
14
|
+
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 50
|
15
|
+
- git clone https://git.tasktools.org/scm/tm/task.git
|
16
|
+
- cd task
|
17
|
+
- git checkout $TASK_VERSION
|
18
|
+
- git clean -dfx
|
19
|
+
- cmake .
|
20
|
+
- make
|
21
|
+
- sudo make install
|
22
|
+
- pwd
|
23
|
+
- cd ../
|
24
|
+
- ls /home/travis/build/dropofwill/rtasklib/spec/data/.task
|
25
|
+
- export TASKRC=/home/travis/build/dropofwill/rtasklib/spec/data/.taskrc
|
26
|
+
- export TASKDATA=/home/travis/build/dropofwill/rtasklib/spec/data/.task
|
27
|
+
- task _version
|
28
|
+
|
29
|
+
script: 'bundle exec rake'
|
30
|
+
|
31
|
+
notifications:
|
32
|
+
email: false
|
data/Guardfile
ADDED
data/PLAN.md
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
# rtasklib
|
2
|
+
|
3
|
+
## Public API
|
4
|
+
|
5
|
+
|
6
|
+
rtasklib::
|
7
|
+
|
8
|
+
For now require `task --version` > 2.4.0, we can work on backwards
|
9
|
+
compatibility later.
|
10
|
+
|
11
|
+
### TaskWarrior Direct
|
12
|
+
|
13
|
+
* #filter(filter_string)
|
14
|
+
just send random shit to task and handle the probable errors properly
|
15
|
+
`task #{filter_string} export`
|
16
|
+
|
17
|
+
* add(
|
18
|
+
|
19
|
+
### ActiveRecord API
|
20
|
+
|
21
|
+
#### Required
|
22
|
+
|
23
|
+
* #where()
|
24
|
+
takes string or hash arguments
|
25
|
+
strings get past directly to `task <filter> export`
|
26
|
+
returns a chainable relationship, instead of the raw objects of #find
|
27
|
+
|
28
|
+
* #find(uuid | id)
|
29
|
+
#find([uuid | id, ...])
|
30
|
+
|
31
|
+
* #take(num=1)
|
32
|
+
|
33
|
+
* #first(num=1)
|
34
|
+
#last(num=1)
|
35
|
+
Num is how many to return
|
36
|
+
Should this be by urgency or id or uuid?
|
37
|
+
|
38
|
+
* #find_by(key: value)
|
39
|
+
e.g. #find_by(project: "LinuxDev") which is the same as
|
40
|
+
#where(project: "LinuxDev").take or .first
|
41
|
+
|
42
|
+
#find_by!(key: value) is the same, but throws an error
|
43
|
+
|
44
|
+
* #all()
|
45
|
+
|
46
|
+
* #order()
|
47
|
+
either sql style string or AR style hash:
|
48
|
+
"created at DESC" or created_at: :desc
|
49
|
+
Chainable on relations from #where
|
50
|
+
|
51
|
+
* #select() alias #project()
|
52
|
+
same as #order()
|
53
|
+
Only returns the listed columns
|
54
|
+
|
55
|
+
* #distinct()
|
56
|
+
same as #take() but for selection/projections
|
57
|
+
|
58
|
+
* #limit(num)
|
59
|
+
maximum number of rows to return
|
60
|
+
|
61
|
+
* #offset(num)
|
62
|
+
change the starting point of a query
|
63
|
+
|
64
|
+
* #readonly()
|
65
|
+
|
66
|
+
* #find_or_create_by(), #find_or_create_by!()
|
67
|
+
|
68
|
+
* #count alias #size, #length
|
69
|
+
|
70
|
+
* #pluck()
|
71
|
+
|
72
|
+
* #exists?()
|
73
|
+
|
74
|
+
* #explain() show underlying command structure
|
75
|
+
|
76
|
+
|
77
|
+
#### Probs should, but nah
|
78
|
+
|
79
|
+
* Optimistic or Pessimistic locking to prevent race conditions
|
80
|
+
|
81
|
+
* Eager loading
|
82
|
+
|
83
|
+
* Scopes
|
84
|
+
|
85
|
+
|
86
|
+
#### Maybe
|
87
|
+
|
88
|
+
* #average(), #minimum(), #maximum(), #sum()
|
89
|
+
|
90
|
+
* #none(), returns an empty relation, useful in chains perhaps?
|
91
|
+
|
92
|
+
* #find_each(), #find_in_batches()
|
93
|
+
load all tasks in in batches, unnecessary for the task interface?
|
94
|
+
|
95
|
+
* #joins()? Maybe for dependencies?
|
96
|
+
|
97
|
+
* #group()? Out of scope probably? Needs a motivating use case.
|
98
|
+
|
99
|
+
* #having()? Require group to be working first
|
100
|
+
|
101
|
+
* #unscope, #only, #reorder, #reverse_order, #rewhere
|
102
|
+
These exist for performance reasons in SQL, probably not necessary for us.
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
## [TaskWarrior 3rd-Party Guidelines]()
|
107
|
+
|
108
|
+
Taskwarrior can be extended by means of a third-party application. There are script examples of import and export add-ons that support many different formats (clone the repository, look in task.git/scripts/add-ons). Then there are more sophisticated applications such as Vit that provide a complete replacement UI.
|
109
|
+
|
110
|
+
All of these provide interesting new features and improve ease of use for different kinds of users. We encourage you to create such add-ons, but in doing so, there are some rules that must be followed, which will not only protect the users data from mistreatment, but also your application from being sensitive to changes in Taskwarrior.
|
111
|
+
|
112
|
+
### Rules
|
113
|
+
|
114
|
+
* Produce, consume and handle UTF8 text properly. UTF8 is the only text encoding supported by Taskwarrior.
|
115
|
+
|
116
|
+
* Don't attempt to parse the pending.data file. Here's why: the .data file format is currently on its fourth version. The very first version was never released, so if you want to read Taskwarrior data properly, you will need to parse the three supported formats. Those formats are not documented. Additionally, you will need to handle the GC operations, implement the task "unwait" feature, observe user defined attribute handling restrictions, and implement recurring task synthesis all of which require .taskrc and default value access. You would essentially be rewriting the data access and configuration portion of Taskwarrior, which is a major undertaking. To support filters you would also need to evaluate the supported clauses, provide DOM access and implement aliases. Then there is also the fifth data format, which is planned...
|
117
|
+
|
118
|
+
* Use the export command to query data from Taskwarrior. The export command implements filters which you can use, or you can omit a filter, get all the data, and implement your own filtering. JSON parsing is very well supported in all relevant programming languages, which means you should be using Taskwarrior itself to query the data, with a commodity JSON parser in conjunction. While the JSON format will be tweaked over time, the general form will not.
|
119
|
+
|
120
|
+
* Use the command line interface to put data into Taskwarrior. Composing a valid command line is a simple way to put data in to Taskwarrior, and the ONLY way to modify data in Taskwarrior.
|
121
|
+
|
122
|
+
* Verify feature support by running task --version. This command returns the version number, which will help you determine whether or not a particular feature is supported. Note that this command does not scan for a configuration file, and is therefore safe to run if Taskwarrior is not yet set up.
|
123
|
+
|
124
|
+
* UDAs (User Defined Attributes) must be preserved in the data. When reading the JSON for a task, there may be attributes that you have never encountered before. If this is the case, you must not modify them in any way. This not only makes your application future-proof, but allows it to tolerate UDAs from other data sources. It also prevents the Taskserver from stripping out your data.
|
125
|
+
Guidelines
|
126
|
+
|
127
|
+
* If you need to store additional data, consider putting your own data file in the ~/.task directory. Just don't use the file names pending.data, completed.data, backlog.data, undo.data or synch.key.
|
128
|
+
|
129
|
+
* There are many helper commands designed to assist add-on scripts such as shell completion scripts. These commands all begin with an underscore, see them with this command: `task help | grep ' _'`.
|
130
|
+
|
131
|
+
* Familiarize yourself with the means of forcing color on or off, disabling word wrapping, disabling bulk operation limitations, disabling confirmation, disabling gc, modifying verbosity and so on. There are ways around almost all the restrictions, and while these don't make sense for regular users, they can be critical for add-on authors.
|
132
|
+
|
133
|
+
## Classes
|
134
|
+
|
135
|
+
```
|
136
|
+
TaskWarriorException => optional?, TaskException
|
137
|
+
|
138
|
+
|
139
|
+
ReadOnlyDictView => external, possibly IceNine
|
140
|
+
|
141
|
+
Deepcopies data to enforce immutability
|
142
|
+
|
143
|
+
|
144
|
+
SerializingObject => internal, Serializer
|
145
|
+
|
146
|
+
TaskResource, TaskFilter < SerialObject
|
147
|
+
|
148
|
+
This is the key user-input -> data step
|
149
|
+
|
150
|
+
Serializing method should hold the following contract:
|
151
|
+
- any empty value (meaning removal of the attribute) is deserialized into a empty string
|
152
|
+
|
153
|
+
- None denotes a empty value for any attribute
|
154
|
+
|
155
|
+
Deserializing method should hold the following contract:
|
156
|
+
- None denotes an empty value for any attribute (however, this is here as a safeguard, TaskWarrior currently does not export empty-valued attributes) if the attribute is not iterable (e.g. list or set), in which case a empty iterable should be used.
|
157
|
+
|
158
|
+
Normalizing methods should hold the following contract:
|
159
|
+
- They are used to validate and normalize the user input. Any attribute value that comes from the user (during Task initialization, assignign values to Task attributes, or filtering by user-provided values of attributes) is first validated and normalized using the normalize_{key} method.
|
160
|
+
|
161
|
+
- If validation or normalization fails, normalizer is expected
|
162
|
+
to raise ValueError.
|
163
|
+
|
164
|
+
Normalize/Serialize/Deserialize the following data inputs:
|
165
|
+
- timestamps, should be localized, so to UTC before string: '%Y%m%dT%H%M%SZ'
|
166
|
+
|
167
|
+
- datetimes/dates, should be localized, default time=midnight
|
168
|
+
|
169
|
+
- annotations, should be an array of hashes
|
170
|
+
|
171
|
+
- tags, 'blah, blah'.split(',')
|
172
|
+
|
173
|
+
- depends, uuid
|
174
|
+
|
175
|
+
- possibly an array of data structure shoehorned into string since UDA doesn't
|
176
|
+
|
177
|
+
- Possible cool use case for meta-programming here.
|
178
|
+
|
179
|
+
TaskResource => internal, TaskResource
|
180
|
+
|
181
|
+
inherits from Serializer
|
182
|
+
|
183
|
+
TaskFilter => internal, TaskFilter
|
184
|
+
|
185
|
+
inherits from Serializer
|
186
|
+
|
187
|
+
TaskAnnotation => internal, TaskAnnotation
|
188
|
+
|
189
|
+
inherits from TaskResource
|
190
|
+
|
191
|
+
Task => internal, Task
|
192
|
+
|
193
|
+
inherits from TaskResource
|
194
|
+
|
195
|
+
TaskQuerySet => internal, TaskLazyLook | TaskLookup
|
196
|
+
|
197
|
+
Lazy lookup for a task object
|
198
|
+
|
199
|
+
TaskWarrior => internal, TaskWarrior
|
200
|
+
|
201
|
+
The main shebang
|
202
|
+
|
203
|
+
|
204
|
+
```
|
data/README.md
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# Rtasklib
|
2
2
|
|
3
|
-
|
3
|
+
[![Coverage Status](https://travis-ci.org/dropofwill/rtasklib.svg?branch=master)](https://travis-ci.org/dropofwill/rtasklib) [![Coverage Status](https://coveralls.io/repos/dropofwill/rtasklib/badge.svg?branch=master)](https://coveralls.io/r/dropofwill/rtasklib?branch=master) [![yard docs](http://b.repl.ca/v1/yard-docs-blue.png)](http://will-paul.com/rtasklib)
|
4
|
+
|
5
|
+
|
6
|
+
## Description
|
7
|
+
|
8
|
+
A Ruby wrapper around the TaskWarrior CLI, based on the Python tasklib. Requires a working TaskWarrior install.
|
4
9
|
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
6
10
|
|
7
11
|
## Installation
|
8
12
|
|
@@ -20,16 +24,33 @@ Or install it yourself as:
|
|
20
24
|
|
21
25
|
$ gem install rtasklib
|
22
26
|
|
27
|
+
|
28
|
+
## Dependencies
|
29
|
+
|
30
|
+
* Taskwarrior > 2.4 (require custom UDAs, recurrences, and duration data types)
|
31
|
+
|
32
|
+
* Ruby > 2 (currently untested on older versions)
|
33
|
+
|
34
|
+
* See `./rtasklib.gemspec` for the latest Ruby dependencies
|
35
|
+
|
36
|
+
|
23
37
|
## Usage
|
24
38
|
|
25
39
|
TODO: Write usage instructions here
|
26
40
|
|
41
|
+
|
27
42
|
## Development
|
28
43
|
|
29
44
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
45
|
|
31
46
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
47
|
|
48
|
+
|
49
|
+
## License
|
50
|
+
|
51
|
+
Release under the MIT License (MIT) Copyright (©) 2015 Will Paul
|
52
|
+
|
53
|
+
|
33
54
|
## Contributing
|
34
55
|
|
35
56
|
1. Fork it ( https://github.com/[my-github-username]/rtasklib/fork )
|
data/Rakefile
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
|
-
|
5
4
|
# run tests with `rake spec`
|
6
5
|
RSpec::Core::RakeTask.new :spec do |task|
|
7
|
-
task.rspec_opts = ["--color", "--format", "
|
6
|
+
task.rspec_opts = ["--color", "--format=doc", "--format=Nc"]
|
8
7
|
end
|
9
8
|
|
10
9
|
task default: :spec
|
data/bin/console
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require "bundler/setup"
|
4
|
-
require "rtasklib"
|
5
4
|
|
6
5
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
6
|
# with your gem easier. You can also use a different console, if you like.
|
8
7
|
|
9
8
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
|
11
|
-
|
9
|
+
require_relative "../lib/rtasklib"
|
10
|
+
require "pry"
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
tw = Rtasklib::TaskWarrior.new("./spec/data/.task")
|
13
|
+
|
14
|
+
binding.pry
|
data/bin/env
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
|
5
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
6
|
+
# with your gem easier. You can also use a different console, if you like.
|
7
|
+
|
8
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
9
|
+
require_relative "../lib/rtasklib"
|
10
|
+
require "pry"
|
11
|
+
|
12
|
+
Pry.start
|
data/bin/setup
CHANGED
File without changes
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "multi_json"
|
2
|
+
require "oj"
|
3
|
+
|
4
|
+
module Rtasklib
|
5
|
+
|
6
|
+
module Controller
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def create
|
10
|
+
end
|
11
|
+
|
12
|
+
def update
|
13
|
+
end
|
14
|
+
|
15
|
+
def get
|
16
|
+
end
|
17
|
+
|
18
|
+
def all
|
19
|
+
all = []
|
20
|
+
Execute.task_popen3(*@override_a, "export") do |i, o, e, t|
|
21
|
+
all = MultiJson.load(o.read).map do |x|
|
22
|
+
Rtasklib::Models::TaskModel.new(x)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
return all
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_rc
|
29
|
+
res = []
|
30
|
+
Execute.task_popen3(*@override_a, "_show") do |i, o, e, t|
|
31
|
+
o.read.each_line { |l| res.push(l.chomp) }
|
32
|
+
end
|
33
|
+
Taskrc.new(res, :array)
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_version
|
37
|
+
version = nil
|
38
|
+
Execute.task_popen3(*@override_a, "_version") do |i, o, e, t|
|
39
|
+
version = to_gem_version(o.read.chomp)
|
40
|
+
end
|
41
|
+
return version
|
42
|
+
end
|
43
|
+
|
44
|
+
# Convert "1.6.2 (adf342jsd)" to Gem::Version object
|
45
|
+
def to_gem_version raw
|
46
|
+
std_ver = raw.chomp.gsub(' ','.').delete('(').delete(')')
|
47
|
+
Gem::Version.new std_ver
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "open3"
|
2
|
+
require "pty"
|
3
|
+
require "expect"
|
4
|
+
require "ruby_expect"
|
5
|
+
require "stringio"
|
6
|
+
|
7
|
+
module Rtasklib
|
8
|
+
|
9
|
+
# How to execute shell commands and capture output
|
10
|
+
module Execute
|
11
|
+
# so that the methods are available within the modules lookup path
|
12
|
+
extend self
|
13
|
+
|
14
|
+
@@exp_regex = {
|
15
|
+
create_rc: %r{Would \s you \s like \s a \s sample \s *.+ \s created, \s
|
16
|
+
so \s taskwarrior \s can \s proceed\? \s
|
17
|
+
\(yes/no\)}x }
|
18
|
+
|
19
|
+
# popen versions
|
20
|
+
#
|
21
|
+
def popen3 program='task', *opts, &block
|
22
|
+
execute = opts.unshift(program)
|
23
|
+
execute = execute.join(" ")
|
24
|
+
p execute
|
25
|
+
|
26
|
+
Open3.popen3(execute) do |i, o, e, t|
|
27
|
+
handle_response(e, t)
|
28
|
+
yield(i, o, e, t) if block_given?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def task_popen3 *opts, &block
|
33
|
+
popen3('task', opts, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def each_popen3 program='task', *opts, &block
|
37
|
+
popen3(program, *opts) do |i, o, e, t|
|
38
|
+
o.each_line do |l|
|
39
|
+
yield(l, i, o, e, t)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def task_each_popen3 *opts, &block
|
45
|
+
popen3(program, *opts) do |i, o, e, t|
|
46
|
+
yield(i, o, e, t)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_response stderr, thread
|
51
|
+
unless thread.value.success?
|
52
|
+
puts stderr.read
|
53
|
+
exit(-1)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Non-greedy json object detection
|
58
|
+
# if /\{.*\}/ =~ l
|
59
|
+
# p l.chomp
|
60
|
+
# res.push(l.chomp)
|
61
|
+
# end
|
62
|
+
# def task create_new, *opts, &block
|
63
|
+
# exp_regex = @@exp_regex
|
64
|
+
# retval = 0
|
65
|
+
# res = nil
|
66
|
+
# buff = ""
|
67
|
+
#
|
68
|
+
# run("task", *opts) do |exp, procedure|
|
69
|
+
# res = procedure.any do
|
70
|
+
# puts exp
|
71
|
+
# expect exp_regex[:create_rc] do
|
72
|
+
# if create_new
|
73
|
+
# send "yes"
|
74
|
+
# else
|
75
|
+
# send "no"
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
# block.call if block_given?
|
79
|
+
# end
|
80
|
+
|
81
|
+
# Filters should be a list of values
|
82
|
+
# Ranges interpreted as ids
|
83
|
+
# 1...5 : "1-5"
|
84
|
+
# 1..5 : "1-4"
|
85
|
+
# 1 : "1"
|
86
|
+
# and joined with ","
|
87
|
+
# [1...5, 8, 9] : "1-5,8,9"
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require "virtus"
|
2
|
+
require "active_model"
|
3
|
+
|
4
|
+
module Rtasklib::Models
|
5
|
+
ValidationError = Class.new RuntimeError
|
6
|
+
|
7
|
+
class UUID < Virtus::Attribute
|
8
|
+
def coerce(value)
|
9
|
+
value.to_s
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
RcBooleans = Virtus.model do |mod|
|
14
|
+
mod.coerce = true
|
15
|
+
mod.coercer.config.string.boolean_map = {
|
16
|
+
'no' => false,
|
17
|
+
'yes' => true,
|
18
|
+
'on' => true,
|
19
|
+
'off' => false }
|
20
|
+
end
|
21
|
+
|
22
|
+
class TaskrcModel
|
23
|
+
# A base Virtus model whose attributes are created dynamically based on the
|
24
|
+
# given attributes are read from a .taskrc or Hash
|
25
|
+
#
|
26
|
+
# Dynamically add convert Boolean Strings to Ruby's Boolean values
|
27
|
+
include RcBooleans
|
28
|
+
end
|
29
|
+
|
30
|
+
class TaskModel
|
31
|
+
include Virtus.model
|
32
|
+
# perhaps use Veto
|
33
|
+
include ActiveModel::Validations
|
34
|
+
|
35
|
+
# Default attributes from TW
|
36
|
+
# Should match: http://taskwarrior.org/docs/design/task.html
|
37
|
+
#
|
38
|
+
# Required for every task
|
39
|
+
attribute :description, String
|
40
|
+
# But on creation these should be set by `task`
|
41
|
+
attribute :status, String
|
42
|
+
attribute :uuid, String
|
43
|
+
attribute :entry, Date
|
44
|
+
|
45
|
+
# Optional for every task
|
46
|
+
attribute :start, Date
|
47
|
+
attribute :until, Date
|
48
|
+
attribute :scheduled, Date
|
49
|
+
attribute :annotation, Array[String]
|
50
|
+
attribute :tags, Array[String]
|
51
|
+
attribute :project, String
|
52
|
+
attribute :depends, String
|
53
|
+
attribute :urgency, Float
|
54
|
+
# is calculated, so maybe private?
|
55
|
+
attribute :priority, String
|
56
|
+
|
57
|
+
# Required only for tasks that are Deleted or Completed
|
58
|
+
attribute :end, Date
|
59
|
+
|
60
|
+
# Required only for tasks that are Waiting
|
61
|
+
attribute :wait, Date
|
62
|
+
|
63
|
+
# Required only for tasks that are Recurring or have Recurring Parent
|
64
|
+
attribute :recur, Date
|
65
|
+
|
66
|
+
# Optional except for tasks with Recurring Parents
|
67
|
+
attribute :due, Date
|
68
|
+
|
69
|
+
# Required only for tasks that have Recurring Child
|
70
|
+
attribute :parent, UUID
|
71
|
+
|
72
|
+
# Internal attributes should be read-only
|
73
|
+
attribute :mask, String
|
74
|
+
attribute :imask, String
|
75
|
+
attribute :modified, Date
|
76
|
+
|
77
|
+
# TODO: handle arbitrary UDA's
|
78
|
+
|
79
|
+
# Refactoring idea, need to understand Virtus internals a bit better
|
80
|
+
# [:mask, :imask, :modified, :status, :uuid, :entry].each do |ro_attr|
|
81
|
+
# define_method("set_#{ro_attr.to_s}") do |value|
|
82
|
+
# self.class.find_by(ro_attr).send(".=", value)
|
83
|
+
# end
|
84
|
+
# end
|
85
|
+
end
|
86
|
+
end
|