lib/Things2THL.rb in zzamboni-things2thl-0.7.0 vs lib/Things2THL.rb in zzamboni-things2thl-0.8.0
- old
+ new
@@ -261,11 +261,11 @@
end ### ThingsNode
####################################################################
class Converter
- attr_accessor :options, :things, :thl
+ attr_accessor :options, :things, :thl, :created
def initialize(opt_struct = nil, things_location = nil, thl_location = nil)
@options=opt_struct || Things2THL.default_options
thingsappname=things_location || 'Things'
thlappname=thl_location || 'The Hit List'
@@ -293,10 +293,18 @@
# Cache of which items are contained in each focus (Inbox, etc.)
# Indexed by focus name, value is a hash with elements keyed by the
# id_ of each node that belongs to that focus. Existence of the key
# indicates existence in the focus.
@cache_focus = {}
+
+ # Statistics
+ @created = {
+ :task => 0,
+ :list => 0,
+ :folder => 0
+ }
+
end
# Get the type of the THL node that corresponds to the given Things node,
# depending on the options specified
def thl_node_type(node)
@@ -358,39 +366,67 @@
else
thl_node_name(parent) + " - loose tasks"
end
end
+ # Simplified version of find_or_create which simply takes :new and :name
+ def simple_find_or_create(what, name, parent = @thl.folders_group.get)
+ find_or_create({:new => what, :with_properties => { :name => name } }, parent)
+ end
+
# Find or create a list or a folder inside the given parent (or the top-level folders group if not given)
- def find_or_create(what, name, parent = @thl.folders_group.get)
- unless what == :list || what == :folder
- raise "find_or_create: 'what' parameter has to be :list or :folder"
+ def find_or_create(props, parent = @thl.folders_group.get)
+ puts "find_or_create: props = #{props.inspect}" if $DEBUG
+ what=props[:new]
+ name=(what==:task) ? props[:with_properties][:title] : props[:with_properties][:name]
+ parentclass=parent.class_.get
+ unless what == :list || what == :folder || what == :task
+ raise "find_or_create: 'props[:new]' parameter has to be :list, :folder or :task"
end
puts "parent of #{name} = #{parent}" if $DEBUG
- if parent.class_.get != :folder
- raise "find_or_create: parent is not a folder, it's a #{parent.class_.get}"
+ if (what == :folder || what == :list) && parentclass != :folder
+ raise "find_or_create: parent is not a folder, it's a #{parentclass}"
+ elsif what == :task && parentclass != :list && parentclass != :task
+ raise "find_or_create: parent is not a list, it's a #{parentclass}"
else
- if parent.groups[name].exists
- parent.groups[name].get
+ if what == :task
+ query = parent.tasks[its.title.eq(name)]
+ if ! query.get.empty?
+ query.get[0]
+ else
+ @created[what]+=1
+ parent.end.make(props)
+ end
else
- parent.end.make(:new => what, :with_properties => {:name => name})
+ query = parent.groups[name]
+ if query.exists
+ query.get
+ else
+ @created[what]+=1
+ parent.end.make(props)
+ end
end
end
end
def new_folder(name, parent = @thl.folders_group.get)
+ @created[:folder]+=1
parent.end.make(:new => :folder,
:with_properties => { :name => name })
end
# Return the provided top level node, or the folders group if the option is not specified
def top_level_node
return @thl.folders_group.get unless options.toplevel
unless @top_level_node
# Create the top-level node if we don't have it cached yet
- @top_level_node=new_folder(options.toplevel)
+ if options.sync
+ @top_level_node=simple_find_or_create(:folder, options.toplevel)
+ else
+ @top_level_node=new_folder(options.toplevel)
+ end
end
@top_level_node
end
# Create (if necessary) and return an appropriate THL container
@@ -414,21 +450,21 @@
result = if options.toplevel
case focusname
when 'Trash', 'Today'
nil
when 'Inbox', 'Next'
- find_or_create(:list, focusname, top_level_node)
+ simple_find_or_create(:list, focusname, top_level_node)
when 'Scheduled', 'Logbook'
- find_or_create((thl_node_type(:project) == :task) ? :list : :folder, focusname, top_level_node)
+ simple_find_or_create((thl_node_type(:project) == :task) ? :list : :folder, focusname, top_level_node)
when 'Someday'
- find_or_create(:folder, focusname, top_level_node)
+ simple_find_or_create(:folder, focusname, top_level_node)
when 'Projects'
if thl_node_type(:project) == :task
- find_or_create(:list, options.projectsfolder || 'Projects', top_level_node)
+ simple_find_or_create(:list, options.projectsfolder || 'Projects', top_level_node)
else
if options.projectsfolder
- find_or_create(:folder, options.projectsfolder, top_level_node)
+ simple_find_or_create(:folder, options.projectsfolder, top_level_node)
else
top_level_node
end
end
else
@@ -441,19 +477,19 @@
when 'Inbox'
thl.inbox.get
when 'Next'
top_level_node
when 'Scheduled', 'Logbook'
- find_or_create((thl_node_type(:project) == :task) ? :list : :folder, focusname, top_level_node)
+ simple_find_or_create((thl_node_type(:project) == :task) ? :list : :folder, focusname, top_level_node)
when 'Someday'
- find_or_create(:folder, focusname, top_level_node)
+ simple_find_or_create(:folder, focusname, top_level_node)
when 'Projects'
if thl_node_type(:project) == :task
- find_or_create(:list, options.projectsfolder || 'Projects', top_level_node)
+ simple_find_or_create(:list, options.projectsfolder || 'Projects', top_level_node)
else
if options.projectsfolder
- find_or_create(:folder, options.projectsfolder, top_level_node)
+ simple_find_or_create(:folder, options.projectsfolder, top_level_node)
else
top_level_node
end
end
when 'Trash', 'Today'
@@ -476,11 +512,11 @@
if node.type == :area
if node.suspended
return top_level_for_focus('Someday')
else
if options.areasfolder
- return find_or_create(:folder, options.areasfolder || 'Areas', top_level_node)
+ return simple_find_or_create(:folder, options.areasfolder || 'Areas', top_level_node)
else
return top_level_node
end
end
end
@@ -489,14 +525,14 @@
foci=focus_of(node)
# ...if not, look at its project and area
if foci.empty?
if node.project?
tl=top_level_for_node(node.project)
- if !tl && node.area?
+ if !tl && node.area? && !options.areas_as
tl=top_level_for_node(node.area)
end
- elsif node.area?
+ elsif node.area? && !options.areas_as
tl=top_level_for_node(node.area)
end
return tl
end
top_level_for_focus(foci)
@@ -528,19 +564,19 @@
# Otherwise, run through the process
container = case node.type
when :area
tlcontainer
when :project
- if options.areas && (options.structure != :projects_areas_as_lists) && node.area?
+ if options.areas && !options.areas_as && (options.structure != :projects_areas_as_lists) && node.area?
get_cached_or_process(node.area)
else
tlcontainer
end
when :selected_to_do
if node.project?
get_cached_or_process(node.project)
- elsif node.area? && options.areas
+ elsif node.area? && options.areas && !options.areas_as
get_cached_or_process(node.area)
else
# It's a loose task
tlcontainer
end
@@ -550,13 +586,13 @@
# Now we check the container type. Tasks can only be contained in lists,
# so if the container is a folder, we have to create a list to hold the task
if container && (container.class_.get == :folder) && (thl_node_type(node) == :task)
if node.type == :project
- find_or_create(:list, options.projectsfolder || 'Projects', container)
+ simple_find_or_create(:list, options.projectsfolder || 'Projects', container)
else
- find_or_create(:list, loose_tasks_name(container), container)
+ simple_find_or_create(:list, loose_tasks_name(container), container)
end
else
container
end
end
@@ -564,21 +600,33 @@
def create_in_thl(node, parent)
if (parent)
new_node_type = thl_node_type(node)
new_node_props = props_from_node(node)
additional_nodes = new_node_props.delete(:__newnodes__)
- result=parent.end.make(:new => new_node_type,
- :with_properties => new_node_props )
+ new_node_spec = {
+ :new => new_node_type,
+ :with_properties => new_node_props }
+ if options.sync
+ result=find_or_create(new_node_spec, parent)
+ else
+ @created[new_node_type]+=1
+ result=parent.end.make(new_node_spec)
+ end
if node.type == :area || node.type == :project
@cache_nodes[node.id_]={}
@cache_nodes[node.id_][:things_node] = node
@cache_nodes[node.id_][:thl_node] = result
end
# Add new nodes
if additional_nodes
additional_nodes.each do |n|
- result.end.make(n)
+ if options.sync
+ find_or_create(n, result)
+ else
+ @created[n[:new]]+=1
+ result.end.make(n)
+ end
end
end
return result
else
parent
@@ -596,15 +644,15 @@
end
container=container_for(node)
puts "Container for #{node.name}: #{container}" if $DEBUG
unless container
- puts "Skipping trashed task '#{node.name}'" unless options.quiet
+ puts "Skipping trashed task '#{node.name}'" unless options.quiet.nonzero?
return
end
- unless (options.quiet)
+ unless (options.quiet.nonzero?)
bullet = (node.type == :area) ? "*" : ((node.status == :completed) ? "✓" : (node.status == :canceled) ? "×" : "-")
puts bullet + " " + node.name
end
newnode=create_in_thl(node, container)
@@ -687,21 +735,38 @@
end
# Process tags
def process_tags(node, prop, inherit_project_tags, inherit_area_tags)
tasktags = node.tags.map {|t| t.name }
+ taskcontexts = []
if inherit_project_tags
# Merge project and area tags
if node.project?
tasktags |= node.project.tags.map {|t| t.name }
if options.areas && node.project.area?
tasktags |= node.project.area.tags.map {|t| t.name }
+ if options.areas_as
+ case options.areas_as
+ when :tags
+ tasktags.push(node.project.area.name)
+ when :contexts
+ taskcontexts.push(node.project.area.name)
+ end
+ end
end
end
end
if options.areas && node.area? && inherit_area_tags
tasktags |= node.area.tags.map {|t| t.name }
+ if options.areas_as
+ case options.areas_as
+ when :tags
+ tasktags.push(node.area.name)
+ when :contexts
+ taskcontexts.push(node.area.name)
+ end
+ end
end
unless tasktags.empty?
# First process time-estimate tags if needed
if options.timetags
# Valid time tags will be deleted from the tags list
@@ -740,10 +805,16 @@
else
"/" + t + (t.index(" ")?"/":"")
end
end].join(' ')
end
+ unless taskcontexts.empty?
+ prop[:title] = [prop[:title], taskcontexts.map do |c|
+ # Contexts cannot have spaces, we also remove any initial @'s before adding our own.
+ "@" + c.gsub(/^@+/, "").gsub(/ /, '_')
+ end].join(' ')
+ end
end
# Check if node is in the Today list
def check_today(node, prop)
if in_focus?('Today', node)
@@ -833,16 +904,19 @@
options=OpenStruct.new
options.completed = false
options.database = nil
options.structure = nil
options.areas = true
- options.quiet = false
+ options.quiet = 0
options.archivecompleted = true
options.projectsfolder = nil
options.areasfolder = nil
options.contexttagsregex = '^@'
options.timetagsregex = '^(\d+)(min|sec|hr)$'
options.timetags = false
+ options.areas_as = nil
+ options.sync = false
+ options.inboxonly = false
return options
end
def Things2THL.new(opt_struct = nil, things_db = nil, thl_location = nil)
Converter.new(opt_struct, things_db, thl_location)