A group merge view would be handy

I can’t answer for @pete31, of course. But I can try to illustrate what they meant by “You need to recursively get each subgroup’s childen” in a JavaScript snippet – AppleScript is outside of my comfort zone, but I’m sure you get the gist:

const allChildren = [];
function getChildrenOfGroup(g) {
  /* g must be a group. Loop over all its children */
  g.children().forEach((c) => {
    if (c.type() === "group") {
      /* The current child is a group -> call this function again to get all its children */
      getChildrenOfGroup(c);
     /* if you want to _keep_ all groups, too, add this child to the allChildren list 
       before calling      getChildrenOf Group again */
    } else {
     /* The current child is no group -> append it to the list of children */
      allChildren.push(c);
    }
  })
}

Now, you can of course write any recursive function non-recursively, so it’s not necessary to do it as I did here.

The idea of a recursive function is that it calls itself until there’s nothing more to act upon. Like here: It calls itself for every group it encounters and only copies the non-groups to a global array (which is probably not a good design choice, but given my comfort zone …).

There’s a bunch of explanations about recursion out there. Wikipedia and your favorite search engine probably tell you more about them than you ever wanted to know.

2 Likes

Thanks for the explanation @chrillek.

I do understand the concept of a recursive function and this helped to reinforce it.

The issue is that the script is relatively complex as-is and the output would need to - in addition - consider the transclusions of items in the main group as well as those from the subgroups (after recursively processing all contained items for each) and then combine everything into a single output file. I need to get a more solid understanding of AppleScript fundamentals before I can do all that on my own.

Since my tongue-in-cheek comment on this being outside my “comfort zone” has been repeatedly referenced in subsequent responses: I meant that it’s beyond my current capabilities after investing 3 hours and really trying everything I can think of trial-and-error style, not that I’m unwilling to try myself because it seems difficult.

No worries :slight_smile:

(If you could specify where you’re stuck other users are probably happy to take a look.)

This is the current iteration of the script. It doesn’t produce errors in DT or script editor but also doesn’t create a transclusion item.

-- Smart Rule script - Create or update transclusion record
on performSmartRule(theGroups)
	tell application id "DNtp"
	
		try
		
			repeat with thisGroup in theGroups
				set thesubGroups to (children of thisGroup whose type = group)
				set theDirectChildren to (children of thisGroup whose type = markdown and comment is not "Transcluded group contents" and name does not contain "auto-transclusion")
				if theDirectChildren ≠ {} then
					set theListDirectChildren to {}
					repeat with thisChild in theDirectChildren
						try
							set thisChild_CustomMetaData to custom meta data of thisChild
							set thisChild_CustomMetaData_Value to mdinsight_relations of thisChild_CustomMetaData -- replace mddescription with "md" + your identifier (see Preferences > Data)
						on error
							set thisChild_CustomMetaData_Value to ""
						end try
						set end of theListDirectChildren to {|SortBy|:thisChild_CustomMetaData_Value, |RefURL|:(reference URL of thisChild), |NameWithoutExtension|:(name without extension of thisChild)}
					end repeat
				end if
			end repeat
			
			repeat with thisGroup in thesubGroups
				set theSubgroupChildren to (children of thisGroup whose type = markdown and comment is not "Transcluded group contents" and name does not contain "auto-transclusion")
				if theSubgroupChildren ≠ {} then
					set theListSubgroupChildren to {}
					repeat with thisChild in theSubgroupChildren
						try
							set thisChild_CustomMetaData to custom meta data of thisChild
							set thisChild_CustomMetaData_Value to mdinsight_relations of thisChild_CustomMetaData -- replace mddescription with "md" + your identifier (see Preferences > Data)
						on error
							set thisChild_CustomMetaData_Value to ""
						end try
						set end of theListSubgroupChildren to {|SortBy|:thisChild_CustomMetaData_Value, |RefURL|:(reference URL of thisChild), |NameWithoutExtension|:(name without extension of thisChild)}
					end repeat
				end if
			end repeat
			
			set theList to (theListSubgroupChildren & theListDirectChildren)
			set theList_sorted to script "AutoTransclusion_Script Library"'s sortedArrayUsingDescriptors(theList, true) -- replace TEST with your Script Library's name
			set theResults to search "kind:markdown comment:\"Transcluded group contents\"" in thisGroup
			
			if theResults ≠ {} then
				set theTransclusionRecord to item 1 of theResults
			else
				set theTransclusionRecord to create record with {name:(name of thisGroup) & " Auto-Transclusion", type:markdown, source:"", comment:"Transcluded group contents"} in thisGroup
			end if
			
			set theTransclusionRecord_Source_list to {}
			
			repeat with i from 1 to (count of theList_sorted)
				set end of theTransclusionRecord_Source_list to linefeed & linefeed & "<br>" & linefeed & "## " & (|NameWithoutExtension| of item i of theList_sorted) & ":" & linefeed & linefeed & "{{" & (|RefURL| of item i of theList_sorted) & "}}"
			end repeat
			
			set theTransclusionRecord_Source to my tid(theTransclusionRecord_Source_list, linefeed & linefeed & linefeed)
			set plain text of theTransclusionRecord to theTransclusionRecord_Source
		on error error_message number error_number
			if the error_number is not -128 then display alert "DEVONthink" message error_message as warning
			return
		end try
	end tell
end performSmartRule


on tid(theInput, theDelimiter)
	set d to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	if class of theInput = text then
		set theOutput to text items of theInput
	else if class of theInput = list then
		set theOutput to theInput as text
	end if
	set AppleScript's text item delimiters to d
	return theOutput
end tid

You define a variable setSubGroups inside your first loop exactly count of theGroups times without using it even once. Instead, you use it in the next repeat loop.

I suggest to first clean up the repetitive stuff, i.e. move it into subroutines (“handlers” in AppleScript lingo, I think). And then rewrite your first repeat loop as a recursive function that

  • calls itself for all elements of thesubGroups (what’s the difference between thesomething and thissomething, anyway? And why is it not theSubGroups?)
  • does all this magical stuff with user custom metadata (there’s BTW a method to retrieve the value of those – easier than the longish thisChild_CustomMetadata_Value armageddon) for non-group children and appends the new object to theList.

This has nothing to do with the transclusion thingies or what not: All that happens well after you’ve build your theListSubgroupChildren and theListDirectChildren and merged them into a simple theList. That’s exactly what the sketchy function below does: it assembles the data in myData for later use.

const myData = [];
function handleGroup(group) {
    const children = children of g;
    children.forEach(c => {
    if (c is a group) {
       handleGroups(c)
   } else {
    md = handleMetaData(c);
    myData.push({sortBy: md.something, RefURL: c.referenceURL(), name: c.nameWithoutExtension()})
    }
  })
}
/* Do some more magic with myData */

This is no real code It’s just meant to illustrate how you might go about it. Although the AppleScript version is probably a bit more convoluted, it’ll certainly look very similar.

Evangelism below

Not to discourage you, but in my mind trying to go about this with all these overly long variable names and utterly broken string manipulations is not making things any easier. They are all distracting from the main tasks at hand.

If you really want to learn a programming language (one that has defined syntax that is) I’d suggest to look elsewhere.

set theList_sorted to script "AutoTransclusion_Script Library"'s sortedArrayUsingDescriptors(theList, true) -- replace TEST with your Script Library's name

then becomes

const sortedList = list.sort((a,b) => {a.sortBy - b.sortBy});

and doesn’t need an external library. And

repeat with i from 1 to (count of theList_sorted)
set end of theTransclusionRecord_Source_list to linefeed & linefeed & "<br>" & linefeed & "## " & (|NameWithoutExtension| of item i of theList_sorted) & ":" & linefeed & linefeed & "{{" & (|RefURL| of item i of theList_sorted) & "}}"
end repeat

becomes

sortedList.forEach(el => {
  recordSource.push(`\n\n<br>\n## ${el.name)}:\n\n{{${el.RefURL}}}`)
})

Of course, you’re trading in the, this, those, set to and get from for {}[]( and ), but it’s till a lot less typing.

This is a great opportunity for me to dig into AppleScript (or perhaps Javascript) at a time where I can really dedicate myself to it, which is not right now. Based on your feedback @chrillek there’s a good starting point to work from when that time comes - thanks.

For now, I managed to come up with an ultra-hacky workaround using several smart rules that get triggered in a sequence through keyboard maestro. The approach uses the script as-is to transclude any transclusion documents within (subgroups of) a parent group into a new combined transclusion document. This addresses my use case with the caveat that it requires setting up a new smart rule for each group that needs to be reviewed. It’s a satisfactory solution, just not as scalable as I’d like.