diff --git a/lib/rtasklib.rb b/lib/rtasklib.rb index e8f9353..25599bb 100644 --- a/lib/rtasklib.rb +++ b/lib/rtasklib.rb @@ -2,15 +2,15 @@ require_relative "rtasklib/models" require_relative "rtasklib/execute" require_relative "rtasklib/controller" +require_relative "rtasklib/helpers" require_relative "rtasklib/taskrc" -require "open3" require "pathname" module Rtasklib class TaskWarrior - attr_reader :version, :data_location, :taskrc, + attr_reader :version, :data_location, :taskrc, :udas, :override, :override_a, :override_str include Controller @@ -31,6 +31,8 @@ def initialize data="#{Dir.home}/.task", opts = {} @override = Taskrc.new(override_h, :hash) @override_a = override.model_to_rc @taskrc = get_rc + @udas = get_udas + add_uda_to_model udas # Check TaskWarrior version, and throw warning if unavailable begin diff --git a/lib/rtasklib/controller.rb b/lib/rtasklib/controller.rb index a26099e..cd9c852 100644 --- a/lib/rtasklib/controller.rb +++ b/lib/rtasklib/controller.rb @@ -40,6 +40,7 @@ def process_ids ids ids end end + private :process_ids def process_tags tags end @@ -96,14 +97,23 @@ def uda_attr? attr def arbitrary_attr attr, depth: 1 attr.to_s.split("_")[depth] end + private :arbitrary_attr # Returns all attribute string after given depth def deep_attr attr, depth: 2 attr.to_s.split("_")[depth..-1].join("_") end private :deep_attr - + def add_uda_to_model uda_hash, model=Rtasklib::Models::TaskModel + uda_hash.each do |uda| + uda.each do |attr, val| + model.attribute attr, String + end + end + end + private :add_uda_to_model + # Retrieve an array of the uda names # # @return [Array] @@ -163,6 +173,5 @@ def to_gem_version raw Gem::Version.new std_ver end private :to_gem_version - end end diff --git a/lib/rtasklib/helpers.rb b/lib/rtasklib/helpers.rb new file mode 100644 index 0000000..e9d0d7a --- /dev/null +++ b/lib/rtasklib/helpers.rb @@ -0,0 +1,33 @@ + +module Rtasklib + + module Helpers + # make this module a stateless, singleton + extend self + + # Int needs to precede float because ints are also floats + def determine_type value + if boolean? value + return Axiom::Types::Boolean + elsif integer? value + return Integer + elsif float? value + return Float + else + return String + end + end + + def integer? value + value.to_i.to_s == value + end + + def float? value + Float(value) rescue false + end + + def boolean? value + ["on", "off", "yes", "no", "false", "true"].include? value.to_s.downcase + end + end +end diff --git a/lib/rtasklib/models.rb b/lib/rtasklib/models.rb index 0b778ff..933dbb5 100644 --- a/lib/rtasklib/models.rb +++ b/lib/rtasklib/models.rb @@ -1,22 +1,46 @@ require "virtus" require "active_model" +require 'iso8601' +require 'date' module Rtasklib::Models - ValidationError = Class.new RuntimeError - class UUID < Virtus::Attribute - def coerce(value) - value.to_s + # A subclass of the ISO8601::Duration object that use `task calc` to parse + # string names like 'weekly', 'biannual', and '3 quarters' + # + # Modifies the #initialize method, preserving the original string duration + class TWDuration < ISO8601::Duration + attr_accessor :negative + attr_reader :frozen_value + + def initialize input, base=nil + @frozen_value = input.dup.freeze + @negative = false + parsed = `task calc #{input}`.chomp + + if parsed.include?("-") + parsed.gsub!(/\-/, "") + negative = true + else + negative = false + end + + super parsed, base + end + end + + # Custom coercer to change a string input into an TWDuration object + class VirtusDuration < Virtus::Attribute + def coerce(v) + if v.nil? || v.blank? then "" else TWDuration.new(v) end end end RcBooleans = Virtus.model do |mod| mod.coerce = true mod.coercer.config.string.boolean_map = { - 'no' => false, - 'yes' => true, - 'on' => true, - 'off' => false } + 'yes' => true, 'on' => true, + 'no' => false, 'off' => false } end class TaskrcModel @@ -61,7 +85,7 @@ class TaskModel attribute :wait, DateTime # Required only for tasks that are Recurring or have Recurring Parent - attribute :recur, DateTime + attribute :recur, VirtusDuration # Optional except for tasks with Recurring Parents attribute :due, DateTime @@ -74,8 +98,6 @@ class TaskModel attribute :imask, String attribute :modified, DateTime - # TODO: handle arbitrary UDA's - # Refactoring idea, need to understand Virtus internals a bit better # [:mask, :imask, :modified, :status, :uuid, :entry].each do |ro_attr| # define_method("set_#{ro_attr.to_s}") do |value| diff --git a/lib/rtasklib/taskrc.rb b/lib/rtasklib/taskrc.rb index fe3ed84..4a36e7d 100644 --- a/lib/rtasklib/taskrc.rb +++ b/lib/rtasklib/taskrc.rb @@ -123,7 +123,6 @@ def model_to_s # Dynamically add a Virtus attr, detect Boolean, Integer, and Float types # based on the value, otherwise just treat it like a string. - # Int needs to precede float because ints are also floats # Modifies the config instance variable # TODO: May also be able to detect arrays # @@ -132,15 +131,16 @@ def model_to_s # @return [undefined] # @api private def add_model_attr attr, value - if boolean? value - config.attribute attr.to_sym, Axiom::Types::Boolean - elsif integer? value - config.attribute attr.to_sym, Integer - elsif float? value - config.attribute attr.to_sym, Float - else - config.attribute attr.to_sym, String - end + config.attribute(attr.to_sym, Helpers.determine_type(value)) + # if boolean? value + # config.attribute attr.to_sym, Axiom::Types::Boolean + # elsif integer? value + # config.attribute attr.to_sym, Integer + # elsif float? value + # config.attribute attr.to_sym, Float + # else + # config.attribute attr.to_sym, String + # end end private :add_model_attr @@ -194,20 +194,5 @@ def path_exist? path end end private :path_exist? - - def integer? value - value.to_i.to_s == value - end - private :integer? - - def float? value - Float(value) rescue false - end - private :float? - - def boolean? value - ["on", "off", "yes", "no", "false", "true"].include? value.to_s.downcase - end - private :boolean? end end