require "yaml" module Todoloo class TaskList include Enumerable # Lower means higher priority SEVERITY_INDEX = { "FIXME" => 100, "HACK" => 200, "TODO" => 300, "XXX" => 400, "NOTE" => 500, "HBD" => 600 }.freeze UNKNOWN_INDEX = 1_000_000 def initialize @tasks = [] end def add(task) if task.is_a?(Array) task.each { |t| add(t) } else raise ArgumentError, "Task type must be Todoloo::Task: #{task.inspect}" unless task.is_a?(Todoloo::Task) @tasks << task end self end def each to_enum(:each) unless block_given? @tasks.each { |task| yield task } end def sort @tasks.sort_by! do |t| [t.topics, task_severity(t), t.path, t.line, t.column] end self end # Structure: # TOPIC: # TYPE: # - description: # path: def write(path, error: :raise) output = YAML.dump( converted_tasks( error_handler: ErrorHandler.new(error) ) ) if path.is_a?(String) File.write(path, output) else path.write(output) end end private def task_severity(task) SEVERITY_INDEX.fetch(task.type, UNKNOWN_INDEX) end def topics_for_task(task) if task.topics.empty? [""] else task.topics end end def converted_tasks(error_handler) output = {} @tasks.each do |task| output_task = { "description" => FoldedString.new(task.description.to_s), "path" => "#{task.path}:#{task.line}:#{task.column}" } topics_for_task(task).each do |topic| by_type = output[topic.to_s] ||= {} tasks = by_type[task.type.to_s.downcase] ||= [] tasks << output_task.dup end rescue Error => e if task.respond_to?(:line) && task.respond_to?(:column) error_handler.call("#{path}:#{task.line}:#{task.column}: Error: #{e}", original_exception: e) else error_handler.call("#{path}:??:??:Error: #{e}", original_exception: e) end end output end end end