Find & Goto parent groups of all replicants of an item

EDITED: the completed script with the help of one crucial line from @BLUEFROG

I am trying to write a script to identify where are all the replicants of an item and go to one of those groups directly to decide whether I should keep/delete the instance. I am almost there except that I need one line of code to make the targeted item as the selected item in the viewer window. I search through the topics and it seems that “selection” in window 1 is a r/o property?

I write this script because it is easier to see a list of groups (that just focus on replicants) in one popup window, as comparing to seeing the groups from a drop-down list in the inspector bar (small fonts for my eyes and the list shows both reps and dups). And I can keep jumping to each group by running the script again and again. Just a personal preference.

Many thanks in advance!
The code:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application id "DNtp"
	try
		if exists (content record) then
			set theDoc to content record
		else if (count of the selection) is 1 then
			set theDoc to item 1 of ((the selection) as list)
		else
			error ("No document is selected. Please select a document, then try again.")
		end if
		
		set theParents to (parents of theDoc whose tag type is not ordinary tag)
		
		set theList to {}
		repeat with each in theParents
			set theList to my sortlist(theList & {((location of each) & (name of each) as string)})
		end repeat
		
		set theDocParentLoc to (choose from list theList with prompt {"Choose which document to link: " & return & "(only select one)"} default items "" OK button name "Go To")
		
		set theDocParent to get record at (theDocParentLoc as string)
		set root of think window 1 to theDocParent
		-- option 1: set the item as the selected item in the parent group in viewer window
        set selection of think window 1 to {theDoc}
		-- option 2: filter out the item as the only item in the parent group in viewer window
        -- set the search query of think window 1 to "name:~" & (name of theDoc as string) & " scope: selection"

          
	
 
	end try
	
end tell

on sortlist(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortlist

1 Like

You ready for it?

Sure … Hope that’s doable…
Coz at this moment, a clumsy way is to have the script to invoke another search within the group, but I hope that there is a more straight forward way to do it.

EDITED: this line of code may get around with the issue by filtering the item out. But the most ideal outcome is to select the item but also showing the other items in the group.

set the search query of think window 1 to "name:~" & (name of theDoc as string) & " scope: selection"

“Fly. Meet sledgehammer” :wink: :stuck_out_tongue:

I will preface this by saying it is usually not a wise idea to set selections in the interface because it causes people to think wrongly about automation. They want to reproduce the mechanical movements they make in an application and that is almost always a really poor idea. (And yes, this is why this is something I didn’t document, nor do I speak of it often.)

That being said…
set selection of think window 1 to {theDoc}

Thank YOU!

No problem. Use it judiciously :slight_smile:

I thought about that line (as a very wild guess) BUT the trick is the {}! I am curious to know why {} is still needed even when the var theDoc already contains a record?

Perhaps it’s because the selection is a “list” of record and therefore the syntax must choose an item within a list and not choosing a record?

Yes, the selection is a list. Though sometimes an explicit coercion of the selection to a list is also needed.

Many thanks again. Cheers

A much simpler and useful version after a suggestion from @cgrunenberg in this post Experimental: Script to trace a backlink. All groups containing the replicants of an item will be shown in the viewer window. No advance search predicates in search field can produce this result - the DT dictionary is really comprehensive!

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

-- ngan 2019.08.21

tell application id "DNtp"
	try
		if exists (content record) then
			set theDoc to content record
		else if (count of the selection) > 1 then
			set theDoc to item 1 of ((the selection) as list)
		else
			error ("No document is selected. Please select a document, then try again.")
		end if
		
		set theParents to (parents of theDoc whose tag type is not ordinary tag)		
		set search results of viewer window 1 to theParents		
	end try	
end tell
1 Like

Thanks for these scripts Ngan. I often want to see a list of all replicants, and quickly jump to them. While the “instances” tool in the inspector works, it’s not perfect for me — it only lets you jump to the parent folder, and the way it shows the file hierarchy makes it tough (for me) to quickly understand what I am seeing.

I’ve tried both of your scripts, and actually prefer the functionality original one! While I like how the second one uses the search results, you can’t then click on the result to go to that replicant. The first one is clean and quick to use.

Thank you for writing these!

Me too (bad eyesight)! That’s why I wrote the script…:+1:

Ha! But it’s not just eye sight. The Instances tool would be better (INMO) if it not only showed the enclosing groups, but the replicants themselves.

@ngan thank you for sharing this script.

Question for you and I could not figure it out - is it possible to modify this script to have it set the parent to only the selected item from the list and delete the other parent?

I think we need something along these lines
delete parent of theDoc whose id is all of the other parents other than the ones just selected

But I do not know how to iterate over the list items that were not selected and what the exact command to delete parent connection will be. In fact, I need to figure out how to interpret some of these commands from the dictionary which I did look at.

The dictionary portion that I believe is relevant but don’t know how to use.
screenshot_2020-09-17-21-02-57

Did more research, and sounds like I need to loop over the other items in the list and use this command.
screenshot_2020-09-17-21-05-20

Ok so I think I may have figured it out.

If somebody here can confirm that this is the right code then I will run it. Thank you!

repeat with theParent in theParents
    log ((location of theParent) & (name of theParent) as string)
    if location of theParent is not equal to location of theDocParent then
        log ((location of theParent) & (name of theParent) as string)
        delete record theDoc in theParent
    end if
end repeat

For some reason, in my database, I have replicants all over the place. I think it is a function of how items were moved to tags, and they stayed in the inbox of the database at the same time.

Now, to remove some replicants, I wrote a script as below. The script fails at the point where I try to check if a record pointing to a group, is contained in a list of other records that point to groups.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application id "DNtp"
	
	# get selection
	set mySelection to selection
	
	repeat with theItem in mySelection
		# now for every selection, get the list of groups the item belongs to
		# and ask that user select groups that that the item should stay in
		set theParents to (parents of theItem whose tag type is not ordinary tag) # this works for replicant
		
		# create list of location and names of parents of theItem
		set theList to {}
		repeat with each in theParents
			set theList to my sortlist(theList & {((location of each) & (name of each) as string)})
		end repeat
		set theParentNames to theList
		
		set newParentNames to ¬
			(choose from list theParentNames ¬
				with prompt ¬
				"Choose parent locations for -> " & (name of theItem) default items ¬
				"" OK button name ¬
				"Go To" with multiple selections allowed)
		
		# we have names of newParents and now to convert them to the parent records
		set theList to {}
		repeat with each in newParentNames
			set theList to (theList & {get record at (each as string)})
		end repeat
		set newParentRecords to theList
		
		# now remove the parent that is not in the newParentRecords
		repeat with theParent in theParents
			# return (location of theParent)
			# return name of item 1 of newParentRecords
			log theParent
			log "Deciding about " & ((location of theParent) & (name of theParent) as string)
			if newParentRecords does not contain (theParent) then
				log ("Deleting as parent location " & (location of theParent) & (name of theParent) as string)
				# delete record theItem in theParent
			end if
		end repeat
		
	end repeat
	
end tell

# sourced from https://discourse.devontechnologies.com/t/find-goto-parent-groups-of-all-replicants-of-an-item/49317
on sortlist(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortlist

Near the end of the script, I try to check if a record exists in a list of records, and the script errors out. Is it because I cannot compare records like the way I am trying to? What is the best way to achieve the if statement below? Thank you!

The line below is what fails.

if newParentRecords does not contain (theParent) then

This is what is not working for me - checking if one group exists in a list of groups.

tell application id "DNtp"
	# get selection
	set mySelection to selection
	set theItem to item 1 of mySelection
	set theParents to (parents of theItem whose tag type is not ordinary tag) # this works for replicant
	# return item 1 of theParents
	set firstParent to item 1 of theParents
	if firstParent is in theParents then
        # also tried the if statement below
        # if theParents contains firstParent
		return "Contains"
	else
		return "Does not contain"
	end if
	
end tell

Well, I did manage to address the if statement issue, by comparing “location and name” as string instead of trying to compare pointers. Would still like to know if somebody can comment as to why the pointers comparison did not work. May be it is not supposed to because only properties can be compared?

Anyhow, the code that should work. Not yet tested but posting here to change the question and answer it somewhat. Thanks for reading!

 use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

tell application id "DNtp"
	
	# get selection
	set mySelection to selection
	
	repeat with theItem in mySelection
		# now for every selection, get the list of groups the item belongs to
		# and ask that user select groups that that the item should stay in
		set theParents to (parents of theItem whose tag type is not ordinary tag) # this works for replicant
		
		# create list of location and names of parents of theItem
		set theList to {}
		repeat with each in theParents
			set theList to my sortlist(theList & {((location of each) & (name of each) as string)})
		end repeat
		set theParentNames to theList
		
		set newParentNames to ¬
			(choose from list theParentNames ¬
				with prompt ¬
				"Choose parent locations for -> " & (name of theItem) default items ¬
				"" OK button name ¬
				"Go To" with multiple selections allowed)
		
		# we have names of newParents and now to convert them to the parent records
		set theList to {}
		repeat with each in newParentNames
			set theList to (theList & {get record at (each as string)})
		end repeat
		set newParentRecords to theList
		
		# now remove the parent that is not in the newParentRecords
		repeat with theParentName in theParentNames
			# return (location of theParent)
			# return name of item 1 of newParentRecords
			log theParentName
			# log "Deciding about " & ((location of theParent) & (name of theParent) as string)
			log "Deciding about " & theParentName
			# if newParentRecords does not contain (theParent) then
			if theParentName is not in newParentNames then
				# log ("Deleting as parent location " & (location of theParent) & (name of theParent) as string)
				log "Deleting as parent location" & theParentName
				set theParent to (get record at (theParentName as string))
				log "Deleting " & name of theParent as string
				# delete record theItem in theParent
			end if
		end repeat
		
	end repeat
	
end tell

on do_create_uuid_list(recordList)
	set nameList to {}
	repeat with each in recordList
		set nameList to (nameList & {(location of each) & (name of each) as string})
	end repeat
	return nameList
end do_create_uuid_list

# sourced from https://discourse.devontechnologies.com/t/find-goto-parent-groups-of-all-replicants-of-an-item/49317
on sortlist(theList)
	set theIndexList to {}
	set theSortedList to {}
	repeat (length of theList) times
		set theLowItem to ""
		repeat with a from 1 to (length of theList)
			if a is not in theIndexList then
				set theCurrentItem to item a of theList as text
				if theLowItem is "" then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				else if theCurrentItem comes before theLowItem then
					set theLowItem to theCurrentItem
					set theLowItemIndex to a
				end if
			end if
		end repeat
		set end of theSortedList to theLowItem
		set end of theIndexList to theLowItemIndex
	end repeat
	return theSortedList
end sortlist

That’s correct.

1 Like

Thank you for replying.