task_time and rtasklib updates March 8th
Last week I talked about how all existing TaskWarrior
wrappers were not going to satisfy the requirements for task_time
, so I decided to roll my own Ruby wrapper rtasklib
and started working on the JSON to domain object marshalling and vice versa. I realized that I was reinventing the wheel a bit with the domain objects and to some extent with the data type coercions (though obviously some custom coercions will be necessary).
I want to implement a familiar interface for developers to interact with the TW database, like the guys behind the Python taskllb
tried to do with Django's QuerySet. So I looked into writing it as a simple adapter for one of the major Ruby ORMs, ActiveRecord, DataMapper, and ROM (Ruby Object Mapper). The ActiveRecord implementation is not particularly friendly to non-SQL data stores and has been quite a bit of work for people using NoSQL to write adapters for. Datamapper had several plain text adapters written (YAML, CSV, etc.), but the whole project had been pretty much been abandoned for ROM. I spent the most time messing around with ROM, it really is a new paradigm for an ORM (the developers might argue it isn't actually an ORM), but it has not yet hit its 1.0 release and the API keeps changing. So even though they had several plaintext data store adapters implemented, they no longer worked with the current release. And while I think I could probably get it working with a little work, I realized I was spending too much time on it for the value it would bring to this particular project.
However, the time spent with ROM was not a complete loss, because the people working on it have also released part of its core as a separate gem, Virtus, specifically for working with domain objects and providing a framework for coercing data types (as well as handling finalization and circular dependencies).
For the validation layer I'm test driving the Veto gem, I was considering using ActiveModel and just including the validations submodule, but if Veto can do the trick that would remove a fairly heavy dependency.
For reading in the config I was thinking about just using just a simple Hash data structure, which is what the Python wrapper taskw
did. One downfall of this approach is that TW doesn't configs don't actually work like that internally, so we end up with situations like this:
color = on
color.label.sort = gray10
Which cannot be stored as a hash, taskw
admits this and prefers child configs to parent ones. That's why I'm going to treat the .taskrc
file as its own read-only domain object, perhaps generated by the task show
command or by reading the ~/.taskrc
with something like parseconfig
, though since TW uses dot syntax it might be just as simple to implement it myself (something like this quick and dirty gist).
Big Picture
Here's a broad sweeping overview of my currently planned architecture. The following shows the modules, classes, as well as inheritance and composition and some of the class methods. Of course all this is subject to change, but this is where I am at right now.
# Public interface, only part of the application a user should interact with
Rtasklib::TW
# Loads .taskrc in on intialization, finds the data store and custom UDAs
::new(rc="~/.taskrc", config_override={})
# Glue that holds everything together
Rtasklib::Controller
# Domain objects <=> JSON for import/export
Rtasklib::Models
include Virtus.model
::Task
::Taskrc
# Connected with each relevant attribute in the model
Rtasklib::Validations
include Veto.validator
::UUID
::String
::StringArray
::Numeric
::Date
::Duration
# Runs the actual shell commands (using Open3)
Rtasklib::Exectute
include Open3
::write(filter, data)
::read(filter)
::execute(args*)
Rtasklib::ParseConfig
# or possibly
Rtasklib::ParseConfig < ParseConfig
Moving Forward
I hope to have the models 'done' by next week and hopefully enough of the architecture around it done so that I can begin building task_time
against it as quickly as possible, so I can figure out the design flaws earlier rather than later.