Skip to content

Instantly share code, notes, and snippets.

@mvz
Created June 6, 2018 12:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mvz/f781b36eee0fbfb9e36407e75bd21bec to your computer and use it in GitHub Desktop.
Save mvz/f781b36eee0fbfb9e36407e75bd21bec to your computer and use it in GitHub Desktop.
Export Getting Things Gnome tasks to Taskwarrior
#!/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))
@mvz
Copy link
Author

mvz commented Jun 6, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment