Class: Things2THL::Converter

Inherits:
Object
  • Object
show all
Defined in:
lib/Things2THL.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opt_struct = nil, things_location = nil, thl_location = nil) ⇒ Converter

Returns a new instance of Converter.



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/Things2THL.rb', line 268

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'
  begin
    @things = Appscript.app(thingsappname)
    @thl = Appscript.app(thlappname)
  rescue ApplicationNotFoundError => e
    puts "I could not open one of the needed applications: #{e.message}"
    exit(1)
  end

  # Regular expression to match context tags. Compile it here to avoid
  # repetition later on.
  options.contexttagsregex_compiled = Regexp.compile(options.contexttagsregex) if options.contexttagsregex
  # Regular expression to match time-estimate tags. Compile it here to avoid
  # repetition later on.
  options.timetagsregex_compiled = Regexp.compile(options.timetagsregex) if options.timetagsregex
  # Structure to keep track of already create items
  # These hashes are indexed by Things node ID (node.id_). Each element
  # contains a hash with two elements:
  #     :things_node - pointer to the corresponding AS node in Things
  #     :thl_node    - pointer to the corresponding AS node in THL
  @cache_nodes = {}

  # 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

Instance Attribute Details

#createdObject

Returns the value of attribute created.



266
267
268
# File 'lib/Things2THL.rb', line 266

def created
  @created
end

#optionsObject

Returns the value of attribute options.



266
267
268
# File 'lib/Things2THL.rb', line 266

def options
  @options
end

#thingsObject

Returns the value of attribute things.



266
267
268
# File 'lib/Things2THL.rb', line 266

def things
  @things
end

#thlObject

Returns the value of attribute thl.



266
267
268
# File 'lib/Things2THL.rb', line 266

def thl
  @thl
end

Instance Method Details

#add_extra_node(prop, newnode) ⇒ Object



825
826
827
828
# File 'lib/Things2THL.rb', line 825

def add_extra_node(prop, newnode)
  prop[:__newnodes__] = [] unless prop.has_key?(:__newnodes__)
  prop[:__newnodes__].push(newnode)
end

#add_list_notes(node, prop) ⇒ Object

Add a new task containing project notes when the project is a THL list, since THL lists cannot have notes



858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
# File 'lib/Things2THL.rb', line 858

def add_list_notes(node, prop)
  new_node_type = thl_node_type(node)
  # Process notes only for non-areas
  if (node.type != :area)
    # If the node has notes but the THL node is a list, add the notes as a task in there
    # If the node has notes but the THL node is a folder (this shouldn't happen), print a warning
    if node.notes? && new_node_type == :list
      newnode = {
        :new => :task,
        :with_properties => { :title => "Notes for '#{prop[:name]}'", :notes => convert_notes(node.notes) }}
      # Mark as completed if the project is completed
      if node.status == :completed || node.status == :canceled
        newnode[:with_properties][:completed] = true
        archive_completed(newnode[:with_properties])
      end
      add_extra_node(prop, newnode)
    end
    if node.notes? && new_node_type == :folder
      $stderr.puts "Error: cannot transfer notes into new folder: #{node.notes}"
    end
  end
end

#add_notes(node, prop) ⇒ Object

Transfer processed notes



882
883
884
# File 'lib/Things2THL.rb', line 882

def add_notes(node, prop)
  prop[:notes] = convert_notes(node.notes) if node.notes?
end

#add_project_duedate(node, prop) ⇒ Object

When projects are lists, if the project has a due date, we add a bogus task to it to represent its due date, since lists in THL cannot have due dates.



888
889
890
891
892
893
894
895
896
897
898
# File 'lib/Things2THL.rb', line 888

def add_project_duedate(node, prop)
  new_node_type = thl_node_type(node)
  return unless node.type == :project && new_node_type == :list && node.due_date?

  # Create the new node
  newnode = {
    :new => :task,
    :with_properties => { :title => "Due date for '#{prop[:name]}'", :due_date => node.due_date }
  }
  add_extra_node(prop, newnode)
end

#aliastostring(node) ⇒ Object



834
835
836
# File 'lib/Things2THL.rb', line 834

def aliastostring(node)
  hextostring((node/'alias').inner_text)
end

#archive_completed(prop) ⇒ Object

Archive completed/canceled if requested



733
734
735
# File 'lib/Things2THL.rb', line 733

def archive_completed(prop)
  prop[:archived] = true if options.archivecompleted && (prop[:completed] || prop[:canceled])
end

#check_today(node, prop) ⇒ Object

Check if node is in the Today list



819
820
821
822
823
# File 'lib/Things2THL.rb', line 819

def check_today(node, prop)
  if in_focus?('Today', node)
    prop[:start_date] = Time.parse('today at 00:00')
  end
end

#container_for(node) ⇒ Object

Create if necessary and return an appropriate THL container object for the new node, according to the node’s class and options selected. A task in THL can only be contained in a list or in another task. So if parent is a folder and note is a task, we need to find or create an auxiliary list to contain it.



559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# File 'lib/Things2THL.rb', line 559

def container_for(node)
  # If its top-level container is nil, it means we need to skip this node
  tlcontainer=top_level_for_node(node)
  return nil unless tlcontainer

  # Otherwise, run through the process
  container = case node.type
              when :area
                tlcontainer
              when :project
                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 && !options.areas_as
                  get_cached_or_process(node.area)
                else
                  # It's a loose task
                  tlcontainer
                end
              else
                raise "Invalid Things node type: #{node.type}"
              end

  # 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
      simple_find_or_create(:list, options.projectsfolder || 'Projects', container)
    else
      simple_find_or_create(:list, loose_tasks_name(container), container)
    end
  else
    container
  end
end

#convert_notes(notes) ⇒ Object

Process Things notes before adding them to THL.



839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
# File 'lib/Things2THL.rb', line 839

def convert_notes(notes)
  return unless notes
  # First, parse the notes as XML and extract the contents of the <note> tag
  note_html = (Hpricot.XML(notes)/'/note').inner_html
  # Then parse the HTML
  html = Hpricot(note_html)
  # Then swap any <alias> tags with their unencoded content
  # TODO: this is a hack - it would be much better to understand the binary structure of the <alias> tag.
  while html.at('alias')
    html.at('alias').swap( aliastostring(html).gsub(/^.*\000\022\000.(.*?)\000.*$/m, 'file:///\1') )
  end
  # Produce the HTML in "plain text" format, which shows the links in brackets
  result=html.to_plain_text
  # Eliminate double URIs of the form 'URI [URI]' (i.e. when the link text is the URI itself)
  result.gsub(/([\w]+:.*)\s+\[\1\]/, '\1')
end

#create_focuscachesObject

Create the focus caches



668
669
670
671
672
673
674
675
676
677
678
# File 'lib/Things2THL.rb', line 668

def create_focuscaches
  get_focusnames.each { |focus|
    puts "Creating focus cache for #{focus}..." if $DEBUG
    @cache_focus[focus] = {}
    next if focus == "Logbook" && !options.completed
    things.lists[focus].to_dos.get.each { |t|
      @cache_focus[focus][t.id_.get] = true
    }
    puts "   Cache: #{@cache_focus[focus].inspect}" if $DEBUG
  }
end

#create_in_thl(node, parent) ⇒ Object



600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/Things2THL.rb', line 600

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__)
    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|
        if options.sync
          find_or_create(n, result)
        else
          @created[n[:new]]+=1
          result.end.make(n)
        end
      end
    end
    return result
  else
    parent
  end
end

#find_or_create(props, parent = @thl.folders_group.get) ⇒ Object

Find or create a list or a folder inside the given parent (or the top-level folders group if not given)



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/Things2THL.rb', line 377

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 (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 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
      query = parent.groups[name]
      if query.exists
        query.get
      else
        @created[what]+=1
        parent.end.make(props)
      end
    end
  end
end

#fix_completed_canceled(node, prop) ⇒ Object

Things sets both ‘completion_date’ and ‘cancellation_date’ for both completed and canceled tasks, which confuses THL, so we delete the one that should not be there.



720
721
722
723
724
725
726
727
728
729
730
# File 'lib/Things2THL.rb', line 720

def fix_completed_canceled(node,prop)
  if prop[:completed_date] && prop[:canceled_date]
    if prop[:canceled]
      prop.delete(:completed)
      prop.delete(:completed_date)
    else
      prop.delete(:canceled)
      prop.delete(:canceled_date)
    end
  end
end

#focus_of(node) ⇒ Object

Get the focuses in which a task is visible



681
682
683
684
685
686
687
688
689
690
691
692
# File 'lib/Things2THL.rb', line 681

def focus_of(node)
  result=[]
  get_focusnames.each { |focus|
    if in_focus?(focus, node)
      result.push(focus)
    end
  }
  if node.type == :area && node.suspended
    result.push('Someday')
  end
  result
end

#get_cached_or_process(node) ⇒ Object

See if we have processed a node already - in that case return the cached THL node. Otherwise, invoke the process() function on it, which will put it in the cache.



544
545
546
547
548
549
550
551
552
# File 'lib/Things2THL.rb', line 544

def get_cached_or_process(node)
  node_id=node.id_
  if @cache_nodes.has_key?(node_id)
    @cache_nodes[node_id][:thl_node]
  else
    # If we don't have the corresponding node cached yet, do it now
    process(node)
  end
end

#get_focusnames(all = false) ⇒ Object

Get all the focus names



662
663
664
665
# File 'lib/Things2THL.rb', line 662

def get_focusnames(all=false)
  # Get only top-level items of type :list (areas are also there, but with type :area) unless all==true
  @cached_focusnames||=things.lists.get.select {|l| all || l.class_.get == :list }.map { |focus| focus.name.get }
end

#hextostring(hexstr) ⇒ Object



830
831
832
# File 'lib/Things2THL.rb', line 830

def hextostring(hexstr)
  [hexstr.delete(" ")].pack("H*")
end

#in_focus?(focus, node) ⇒ Boolean

Check if a node is in a certain focus Node can be a ThingsNode, and AS node, or a node ID

Returns:

  • (Boolean)


696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
# File 'lib/Things2THL.rb', line 696

def in_focus?(focus, node)
  unless @cache_focus[focus]
    create_focuscaches
  end
  case node
  when ThingsNode
    key = node.id_
  when Appscript::Reference
    key = node.id_.get
  when String
    key = node
  else
    puts "Unknown node object type: #{node.class}"
    return nil
  end
  return @cache_focus[focus].has_key?(key)
end

#loose_tasks_name(parent) ⇒ Object



363
364
365
366
367
368
369
# File 'lib/Things2THL.rb', line 363

def loose_tasks_name(parent)
  if parent == top_level_node
    "Loose tasks"
  else
    thl_node_name(parent) + " - loose tasks"
  end
end

#new_folder(name, parent = @thl.folders_group.get) ⇒ Object



411
412
413
414
415
# File 'lib/Things2THL.rb', line 411

def new_folder(name, parent = @thl.folders_group.get)
  @created[:folder]+=1
  parent.end.make(:new => :folder,
                  :with_properties => { :name => name })
end

#process(node) ⇒ Object

Process a single node. Returns the new THL node.



637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
# File 'lib/Things2THL.rb', line 637

def process(node)
  return unless node
  # Skip if we have already processed it
  return if @cache_nodes.has_key?(node.id_)
  # Areas don't have status, so we skip the check
  unless node.type == :area
    return if ( node.status == :completed || node.status == :canceled ) && !(options.completed)
  end

  container=container_for(node)
  puts "Container for #{node.name}: #{container}" if $DEBUG
  unless container
    puts "Skipping trashed task '#{node.name}'" unless options.quiet.nonzero?
    return
  end
  
  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)
end

#process_tags(node, prop, inherit_project_tags, inherit_area_tags) ⇒ Object

Process tags



738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'lib/Things2THL.rb', line 738

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
      tasktags=tasktags.delete_if do |t|
        if data=options.timetagsregex_compiled.match(t)
          timeestimate = nil
          # If the regex includes only one group, it is assumed to be
          # the time in minutes. If it includes two groups, the second
          # group specifies the time unit, as defined in Constants::TIMEUNITS
          case data.size
          when 0
            puts "Invalid time tags regex, does not return any groups: #{options.timetagsregex_compiled.to_s}"
          when 1
            # Assumed to be in minutes, timeestimate is in seconds
            timeestimate = data[1].to_i * 60
          else
            # Second one is the units - the rest are ignored
            timeunit=Constants::TIMEUNITS[data[2]]
            if !timeunit
              puts "Invalid time estimate tag, I could not match the time unit: '#{t}'"
            else 
              timeestimate = data[1].to_i * timeunit
            end
          end
          if timeestimate
            prop[:estimated_time] = timeestimate
          end
        end
      end
    end
    prop[:title] = [prop[:title], tasktags.map do |t|
                      if options.contexttagsregex_compiled &&
                          options.contexttagsregex_compiled.match(t)
                        # Contexts cannot have spaces, we also remove any initial @'s before adding our own.
                        "@" + t.gsub(/^@+/, "").gsub(/ /, '_')
                      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

#props_from_node(node) ⇒ Object



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/Things2THL.rb', line 325

def props_from_node(node)
  newprops={}
  # Process the properties, according to how the mapping is specified in STRUCTURES
  pm=Constants::STRUCTURES[options.structure][node.type][1]
  proptoset=nil
  pm.keys.each do |k|
    case pm[k]
    when Symbol
      proptoset=pm[k]
      value=node.node.properties_.get[k]
      newprops[proptoset] = value if value && value != :missing_value
    when Array
      proptoset=pm[k][0]
      value=pm[k][1][node.node.properties_.get[k]]
      newprops[proptoset] = value if value && value != :missing_value
    when Hash
      value = node.node.properties_.get[k]
      if value && value != :missing_value
        proptoset = pm[k][value]
        if proptoset
          newprops[proptoset] = true
        end
      end
    else
      puts "Invalid class for pm[k]=#{pm[k]} (#{pm[k].class})"
    end
    puts "Mapping node.#{k} (#{node.node.properties_.get[k]}) to newprops[#{proptoset}]=#{newprops[proptoset]}" if $DEBUG
  end
  # Do any necesary postprocessing
  postproc=Constants::STRUCTURES[options.structure][node.type][2]
  if postproc
    puts "Calling post-processor #{postproc.to_s} with newprops=#{newprops.inspect}" if $DEBUG
    postproc.call(node, newprops, self)
    puts "After post-processor: #{newprops.inspect}" if $DEBUG
  end
  return newprops
end

#simple_find_or_create(what, name, parent = @thl.folders_group.get) ⇒ Object

Simplified version of find_or_create which simply takes :new and :name



372
373
374
# File 'lib/Things2THL.rb', line 372

def simple_find_or_create(what, name, parent = @thl.folders_group.get)
  find_or_create({:new => what, :with_properties => { :name => name } }, parent)
end

#thl_node_name(node) ⇒ Object

Get the name/title for a THL node.



321
322
323
# File 'lib/Things2THL.rb', line 321

def thl_node_name(node)
  node.properties_.get[Constants::TITLEPROP[node.class_.get]]
end

#thl_node_type(node) ⇒ Object

Get the type of the THL node that corresponds to the given Things node, depending on the options specified



310
311
312
313
314
315
316
317
318
# File 'lib/Things2THL.rb', line 310

def thl_node_type(node)
  case node
  when Symbol
    type=node
  else
    type=node.type
  end
  Constants::STRUCTURES[options.structure][type][0]
end

#top_level_for_focus(focusnames) ⇒ Object

Create (if necessary) and return an appropriate THL container for a given top-level Things focus. If –top-level-folder is specified, all of them are simply folders inside that folder. Otherwise:

Inbox => Inbox
Next  => default top level node
Scheduled, Someday => correspondingly-named top-level folders
Logbook => 'Completed' top-level folder
Projects => 'Projects' list if --projects-as-tasks
         => 'Projects' folder if --projects-folder was specified
         => default top level node otherwise
Trash   => ignore
Today   => ignore


446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
# File 'lib/Things2THL.rb', line 446

def top_level_for_focus(focusnames)
  # We loop through all the focus names given, and return the first
  # one that produces a non-nil container
  focusnames.each do |focusname|
    result = if options.toplevel
               case focusname
               when 'Trash', 'Today'
                 nil
               when 'Inbox', 'Next'
                 simple_find_or_create(:list, focusname, top_level_node)
               when 'Scheduled', 'Logbook'
                 simple_find_or_create((thl_node_type(:project) == :task) ? :list : :folder, focusname, top_level_node)
               when 'Someday'
                 simple_find_or_create(:folder, focusname, top_level_node)
               when 'Projects'
                 if thl_node_type(:project) == :task
                   simple_find_or_create(:list, options.projectsfolder || 'Projects', top_level_node)
                 else
                   if options.projectsfolder
                     simple_find_or_create(:folder, options.projectsfolder, top_level_node)
                   else
                     top_level_node
                   end
                 end
               else 
                 puts "Invalid focus name: #{focusname}"
                 top_level_node
               end
             else
               # That was easy. Now for the more complicated part.
               case focusname
               when 'Inbox'
                 thl.inbox.get
               when 'Next'
                 top_level_node
               when 'Scheduled', 'Logbook'
                 simple_find_or_create((thl_node_type(:project) == :task) ? :list : :folder, focusname, top_level_node)
               when 'Someday'
                 simple_find_or_create(:folder, focusname, top_level_node)
               when 'Projects'
                 if thl_node_type(:project) == :task
                   simple_find_or_create(:list, options.projectsfolder || 'Projects', top_level_node)
                 else
                   if options.projectsfolder
                     simple_find_or_create(:folder, options.projectsfolder, top_level_node)
                   else
                     top_level_node
                   end
                 end
               when 'Trash', 'Today'
                 nil
               else 
                 puts "Invalid focus name: #{focusname}"
                 top_level_node
               end
             end
    return result if result
  end
  nil
end

#top_level_for_node(node) ⇒ Object

Get the top-level focus for a node. If it’s not directly contained in a focus, check its project and area, if any.



509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/Things2THL.rb', line 509

def top_level_for_node(node)
  # Areas are always contained at the top level, unless they
  # are suspended
  if node.type == :area
    if node.suspended
      return top_level_for_focus('Someday')
    else
      if options.areasfolder
        return simple_find_or_create(:folder, options.areasfolder || 'Areas', top_level_node)
      else
        return top_level_node
      end
    end
  end

  # Else, find if the node is contained in a top-level focus...
  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? && !options.areas_as
        tl=top_level_for_node(node.area)
      end
    elsif node.area? && !options.areas_as
      tl=top_level_for_node(node.area)
    end
    return tl
  end
  top_level_for_focus(foci)
end

#top_level_nodeObject

Return the provided top level node, or the folders group if the option is not specified



418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/Things2THL.rb', line 418

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
    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