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)