Created
June 6, 2018 12:50
-
-
Save mvz/f781b36eee0fbfb9e36407e75bd21bec to your computer and use it in GitHub Desktop.
Export Getting Things Gnome tasks to Taskwarrior
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'happymapper' | |
require 'json' | |
class Task | |
include HappyMapper | |
attribute :id, String | |
attribute :status, String | |
attribute :tags, String | |
attribute :uuid, String | |
element :title, String | |
element :startdate, String | |
element :duedate, String | |
element :modified, DateTime | |
element :donedate, String | |
has_many :subtasks, String, tag: 'subtask' | |
element :content, String | |
end | |
class TaskList | |
def initialize(tasks) | |
@tasks = tasks | |
@tasks_hash = {} | |
@tasks.each do |task| | |
@tasks_hash[task.id] = task | |
end | |
end | |
def each_task(&block) | |
@tasks.each &block | |
end | |
def find(task_id) | |
@tasks_hash[task_id] | |
end | |
def root_task(task) | |
parent = @tasks.find { |it| it.subtasks.include? task.id } | |
parent && root_task(parent) || task | |
end | |
end | |
class TaskProcessor | |
def initialize(task_list, handler) | |
@task_list = task_list | |
@handler = handler | |
@processed = {} | |
end | |
def process | |
@processed.clear | |
@task_list.each_task do |task| | |
next if @processed[task.id] | |
root = @task_list.root_task(task) | |
process_task root | |
end | |
@task_list.each_task do |task| | |
raise "Task #{task.id} not processed" unless @processed[task.id] | |
end | |
end | |
def self.process(tasks, handler) | |
new(tasks, handler).process | |
end | |
private | |
def process_task(task, level = 0) | |
@handler.handle(task, level) | |
@processed[task.id] = true | |
process_subtasks task.subtasks, level + 1 | |
end | |
def process_subtasks(subtask_ids, level) | |
subtask_ids.each do |task_id| | |
raise "Task #{task_id} already processed" if @processed[task_id] | |
task = @task_list.find(task_id) | |
process_task task, level | |
end | |
end | |
end | |
class TaskWarriorExporter | |
def initialize(task_list) | |
@task_list = task_list | |
end | |
def handle(task, level) | |
status = case task.status | |
when 'Dismiss' | |
'deleted' | |
when 'Done' | |
'completed' | |
when 'Active' | |
'pending' | |
else | |
raise "Unknown: #{task.status}" | |
end | |
data = { | |
description: task.title, | |
status: status, | |
uuid: task.uuid, | |
} | |
if task.duedate | |
if task.duedate == 'soon' | |
data[:priority] = 'H' | |
else | |
data[:due] = task.duedate | |
end | |
end | |
data[:end] = task.donedate if task.donedate | |
data[:scheduled] = task.startdate if task.startdate | |
entry = guess_entry(task) | |
data[:entry] = entry | |
subtask_uuids = task.subtasks.map do |subtask_id| | |
@task_list.find(subtask_id).uuid | |
end | |
if subtask_uuids.any? | |
data[:depends] = subtask_uuids.join(',') | |
end | |
data[:tags] = task.tags unless task.tags.empty? | |
if task.content | |
data[:annotations] = [ { entry: entry, description: task.content } ] | |
end | |
puts data.to_json | |
end | |
private | |
def guess_entry(task) | |
dates = [task.duedate, task.donedate, task.startdate].compact. | |
reject { |it| %w(someday soon).include? it }. | |
sort | |
dates.first || task.modified.to_s | |
end | |
end | |
projects_file = File.expand_path '~/.local/share/gtg/projects.xml' | |
projects = HappyMapper.parse File.read projects_file | |
tasks_file = projects.backend.path | |
tasks = Task.parse File.read tasks_file | |
task_list = TaskList.new tasks | |
TaskProcessor.process(task_list, TaskWarriorExporter.new(task_list)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Importing GTG tasks into Taskwarrior