Георги обнови решението на 01.11.2013 19:03 (преди около 11 години)
+module TodoList
+
+ TASK_FORMAT = /
+ ^
+ (.+?) \| # Status
+ (.+?) \| # Message
+ (.+?) \| # Priority
+ (.*?) # Comma-separated list of tags
+ $
+ /x
+
+ class Task < Struct.new(:status, :description, :priority, :tags)
+ def self.new_from_raw_data(status, description, priority, tags)
+ Task.new(status.strip.downcase.to_sym,
+ description.strip,
+ priority.strip.downcase.to_sym,
+ tags.split(',').map(&:strip))
+ end
+
+ def to_s
+ "%s | %s | %s | %s" % [status, description, priority, tags]
+ end
+ end
+
+ class List
+ include Enumerable
+
+ def initialize(tasks = [])
+ @tasks = tasks
+ end
+
+ def each(&block)
+ @tasks.each &block
+ end
+
+ def filter(criteria)
+ List.new(select { |task| criteria.valid_for?(task) })
+ end
+
+ def adjoin(other)
+ List.new(tasks + other.tasks)
+ end
+
+ def tasks_todo
+ count { |task| task.status == :todo }
+ end
+
+ def tasks_in_progress
+ count { |task| task.status == :current }
+ end
+
+ def tasks_completed
+ count { |task| task.status == :done }
+ end
+
+ def completed?
+ tasks_completed == tasks.size
+ end
+
+ protected
+ attr_reader :tasks
+ end
+
+ module Criteria
+ module LogicalOperators
+ def &(other)
+ Conjunction.new(self, other)
+ end
+
+ def |(other)
+ Disjunction.new(self, other)
+ end
+
+ def !
+ Negation.new(self)
+ end
+ end
+
+ class ByStatus
+ include LogicalOperators
+
+ def initialize(status)
+ @status = status
+ end
+
+ def valid_for?(task)
+ task.status == @status
+ end
+ end
+
+ class ByPriority
+ include LogicalOperators
+
+ def initialize(priority)
+ @priority = priority
+ end
+
+ def valid_for?(task)
+ task.priority == @priority
+ end
+ end
+
+ class ByTags
+ include LogicalOperators
+
+ def initialize(tags)
+ @tags = tags
+ end
+
+ def valid_for?(task)
+ (@tags - task.tags).empty?
+ end
+ end
+
+ class Conjunction
+ include LogicalOperators
+
+ def initialize(criteria1, criteria2)
+ @criteria1 = criteria1
+ @criteria2 = criteria2
+ end
+
+ def valid_for?(task)
+ @criteria1.valid_for?(task) and @criteria2.valid_for?(task)
+ end
+ end
+
+ class Disjunction
+ include LogicalOperators
+
+ def initialize(criteria1, criteria2)
+ @criteria1 = criteria1
+ @criteria2 = criteria2
+ end
+
+ def valid_for?(task)
+ @criteria1.valid_for?(task) or @criteria2.valid_for?(task)
+ end
+ end
+
+ class Negation
+ include LogicalOperators
+
+ def initialize(criteria)
+ @criteria = criteria
+ end
+
+ def valid_for?(task)
+ not @criteria.valid_for?(task)
+ end
+ end
+ end
+
+end
+
+# Public interface
+
+module TodoList
+
+ def self.parse(text)
+ todos = text.scan(TASK_FORMAT).map do |raw_task_data|
+ Task.new_from_raw_data(*raw_task_data)
+ end
+
+ List.new(todos)
+ end
+
+end
+
+module Criteria
+
+ def self.status(status)
+ TodoList::Criteria::ByStatus.new(status)
+ end
+
+ def self.priority(priority)
+ TodoList::Criteria::ByPriority.new(priority)
+ end
+
+ def self.tags(tags)
+ TodoList::Criteria::ByTags.new(tags)
+ end
+
+end