collecting all sub-handlers invoked by the first

It’s easy to get the contents of a handler-block and the my-handler-blocks referencing to the first block, as I can build it in an “mechanical” way.

But I wanna get all sub-handler-blocks connected to the first handler-block
eg. lets say handler block 1 contains 2 my handler, those my-handlers contain more my handler - I need to collect all blocks connected to the first block), and therefore my code needs to be dynamic, looking recursively not just in down to two levels

I’m wondering how to approach my problem. Maybe with those incredible sorting routines of Nigel Garvey? (sweat) How to deal with them?

Can you provide some example code here to clarify what you mean? An handler-block is nothing more than code between the on handlerName and end handlerName placeholders. I assume you’re referring to something else than a handler-block, right?

Don’t you mean inheritance in script objects? I mean something like this where you create the parenting chain at run time and not at compile time?

on createParent(_parent)
	script --anonymous 
		property parent : _parent
		property description : "parent"
		
		on getDescription()
			return my description
		end getDescription
	end script
end createParent

on createChild(_parent)
	script --anonymous 
		property parent : _parent
		property description : "child object"
		
		on getDescription()
			return description
		end getDescription
		
		on getParentDescription()
			parent's getDescription()
		end getParentDescription
	end script
end createChild

set p to createParent(me)
set c1 to createChild(p)
set c2 to createChild(p)

return {p's getDescription(), c1's getParentDescription(), c2's getParentDescription()}

To me it sounds like he’s just asking for a meta-script that would pull a handler and all additional handlers the first handler depends on (regardless of how deep the nesting goes) out of a script.

IE:
-you run a script

  • it asks you to select the script you’d like to operate on
  • it presents a list of all handlers in that script and asks you to select one
  • it creates a new script file that contains the handler you selected, and every other handler referenced by the one you selected, and every handler referenced by those handlers, recursive until all possible dependencies are included.

Let me know if that’s correct… if so, it doesn’t sound all that bad to script… except I’m thinking it might be hard to detect and avoid possible loop situations.

  • Tom.

DJ Bazzie Wazzie
:cool:
t.spoon was right with his assumption

This is the piece of code i wrote. Its not a final solution, but a limited code, as its not good to collect more than a handler and its sub-handlers. Stopping there - need some input how to improve that and collect all handlers.:o

set Appscr_app to "Script Editor"
tell application Appscr_app
	tell document 2 #1
		#collect handlers
		set all_ppr to paragraphs
		set dd to 0
		set ls to {}
		repeat with a in all_ppr
			set dd to dd + 1
			set a to a as text
			if a begins with "on " then
				if "(" is in a then
					set get_routine to text 4 thru ((offset of "(" in a) - 1) of a
				else
					set get_routine to text 4 thru -2 of a
				end if
				if number of words of get_routine = 2 then
					set get_routine to word 1 of get_routine
				end if
				
				copy (dd & " • " & get_routine as text) to end of ls
			end if
		end repeat
		
		#set ls to ls as text
		#return nrhdl
		if ls is "" then return
		
		activate
		set ch to choose from list ls with prompt "Wähle Block den Ihr kopieren wollt..." default items (item 1 of ls)
		if ch is false then return
		set ch to ch as text
		set get_offset to (offset of " • " in ch)
		set find_parag to text 1 thru (get_offset - 1) of ch as number
		set handlerNm to text (get_offset + 3) thru -1 of ch as text
		set cc_all_ppr to number of items in all_ppr
		set {copy_handler, my_hdlLs} to {{}, {}}
		repeat with b from find_parag to cc_all_ppr
			set get_parag to item b of all_ppr
			if get_parag begins with "end " & handlerNm then
				copy get_parag to end of copy_handler
				exit repeat
			else
				if " my " is in get_parag or "my " is in get_parag and "(" is in get_parag then
					copy b to end of my_hdlLs
				end if
				copy get_parag to end of copy_handler
			end if
		end repeat
		set copy_handler to (copy_handler as text)
		#return copy_handler
	end tell
	
	#copy all my handlers connected to our handler if any
	set copy_handler to (copy_handler as text)
	if my_hdlLs is not {} then
		#log my_hdlLs
		repeat with a in (my_hdlLs as list) #just paragraph numbers
			set get_ppg to (item a of all_ppr)
			#log get_ppg
			set all_words to words of get_ppg
			#log all_words
			
			#find my in a paragraph
			set dd to 0
			repeat with b in all_words
				set dd to dd + 1
				set b to b as text
				if b is "my" then
					set handlerVar to item (dd + 1) of all_words
					exit repeat
				end if
			end repeat
			#log handlerVar
			
			set bb to 0
			repeat with c in ls
				set c to c as text
				set bb to bb + 1
				if handlerVar is in c then
					set get_offset to (offset of " • " in c)
					set handlerNm to text (get_offset + 3) thru -1 of c as text
					set find_parag to text 1 thru (get_offset - 1) of c as number
					exit repeat
				end if
			end repeat
			#log handlerNm
			#log find_parag
			
			set copy_Myhandler to {}
			repeat with d from find_parag to cc_all_ppr
				set get_parag to item d of all_ppr
				if get_parag begins with "end " & handlerNm then
					copy get_parag to end of copy_Myhandler
					exit repeat
				else
					copy get_parag to end of copy_Myhandler
				end if
			end repeat
			#exit repeat
			set copy_handler to copy_handler & return & (copy_Myhandler as text)
		end repeat
		
	end if
	#return copy_handler
	
	set res to make new document with properties {contents:copy_handler}
	compile res
end tell

Here’s an effort using the Satimage OSAX. It could no doubt be done with ASObjC instead, but with more code. Caveats at the top:

(*
	Choose a handler from a script document in Script Editor and copy it and any other handlers used during its execution to a new document.
	
	THIS SCRIPT REQUIRES THE SATIMAGE OSAX (<http://www.satimage.fr/software/en/downloads/downloads_companion_osaxen.html>).
	It's only intended for use where all the handlers are at the top level of the source script. It's not recommended for handlers in script objects, although it may sometimes work with them.
	Not fully proofed against text values or barred labels containing characters which might fool the regices.
	Only handlers are inserted into the new script. Any required properties, 'use' statements, or script objects are your own problem!
*)

on getHandlerAndSubhandlers(scriptText)
	script o
		property handlerMatches : missing value
		property handlerCount : missing value
		property handlerTextList : missing value
		property matchIndexList : missing value
		
		on doTheBusiness(scriptText)
			-- Get the text of all the handlers in the script and, as separate entities, the text after "end " in their last lines.
			set handlerMatches to (find text "(?m)^[ \\t]*+(?:on (?!error)|to )([[:alnum:]_\\|]++)(?:.(?!end \\1))++.end (\\1[^\\R]*+)" in scriptText using {0, 2} regexpflag {"MULTILINE"} with regexp, string result and all occurrences)
			-- Build a list of the "end" labels to present to the user.
			set labelList to {}
			set handlerCount to (count handlerMatches)
			repeat with i from 1 to handlerCount
				set thisMatch to item i of handlerMatches
				set end of labelList to (i as text) & " • " & end of thisMatch
			end repeat
			if (labelList is {}) then display dialog "\"No handlers found in the script.\" in German!" buttons {"OK"} default button 1 cancel button 1 with icon stop
			
			-- Get the user's choice of "top" handler and start a list of handler texts with it.
			set cflResult to (choose from list labelList with prompt "Wähle Block den Ihr kopieren wollt..." default items {item 1 of labelList})
			if (cflResult is false) then error number -128
			set chosenHandlerMatchIndex to (word 1 of beginning of cflResult) as integer
			set chosenHandlerMatch to item chosenHandlerMatchIndex of handlerMatches
			set chosenHandlerText to beginning of chosenHandlerMatch
			set handlerTextList to {chosenHandlerText}
			set matchIndexList to {chosenHandlerMatchIndex}
			
			-- Add the texts of any other handlers called during the execution of the chosen one.
			addSubhandlers()
			
			-- Return a single text containing all the handler texts collected.
			return (join handlerTextList using (linefeed & linefeed))
		end doTheBusiness
		
		on addSubhandlers() -- Recursive handler to identify and add any subhandlers called by the most recent addition to handlerTextList.
			set callingHandlerText to item -1 of handlerTextList
			repeat with i from 1 to handlerCount
				-- Only check handlers which aren't in handlerTextList already.
				if (i is not in matchIndexList) then
					-- Get the full text and "end" label of each.
					set thisMatch to item i of my handlerMatches
					set {thisHandlerText, handlerEndLabel} to thisMatch
					-- Make up a suitable regex to identify any call to this handler in the current calling handler.
					if (handlerEndLabel contains ":") then
						-- Interleaved parameters. All must be in the call.
						set handlerCallRegex to (change {"[|(){}]", ":", " $"} into {"\\\\\\0", ":(?:\"[^\"]*+\"|\\\\{[^\\\\}]*+\\\\}|\\\\([^\\\\)]*+\\\\)|\\\\|[^\\\\|]*+\\\\||[^[:space:]]++) ", "(?:(?! )|(?= [-#(]))"} in handlerEndLabel with regexp)
					else
						-- Not interleaved parameters. Check for parenthesis in the top line of the handler.
						set handlerTopLine to paragraph 1 of thisHandlerText
						if (handlerTopLine contains "(") then
							-- Positional parameters. The handler label must be followed by parenthesis in the call.
							set handlerCallRegex to handlerEndLabel & "\\([^\\)]*+\\)"
						else
							-- Labelled parameters. The handler label and the first parameter label must both be in the call.
							set handlerCallRegex to (change "^\\s*+(?:on|to) ([^ ]++)( [^ ]++).++" into "\\1.*\\2" in handlerTopLine with regexp)
						end if
					end if
					-- Apply the regex to the text of the calling handler. If any hits, store this subhandler text and recurse to check _its_ subhandlers.
					if ((find text handlerCallRegex in callingHandlerText with regexp and all occurrences) is not {}) then
						set end of my handlerTextList to thisHandlerText
						set end of my matchIndexList to i
						addSubhandlers()
					end if
				end if
			end repeat
		end addSubhandlers
	end script
	
	return o's doTheBusiness(scriptText)
end getHandlerAndSubhandlers

tell application "Script Editor" to set scriptText to text of document 2 #1
set extractedHandlersText to getHandlerAndSubhandlers(scriptText)
tell application "Script Editor" to compile (make new document with properties {text:extractedHandlersText})

Edit: Minor improvements and fixes.

As here:

(*
	Choose a handler from a script document in Script Editor and copy it and any other handlers used during its execution to a new document.
	
	This version of the script uses ASObjC and required Mac OS 10.10 (Yosemite) or later.
	It's only intended for use where all the handlers are at the top level of the source script. It's not recommended for handlers in script objects, although it may sometimes work with them.
	Not fully proofed against text values or barred labels containing characters which might fool the regices.
	Only handlers are inserted into the new script. Any required properties, 'use' statements, or script objects are your own problem!
*)

on getHandlerAndSubhandlers(scriptText)
	script o
		use AppleScript version "2.4" -- Yosemite (10.10) or later
		use framework "Foundation"
		use scripting additions
		
		property |⌘| : current application
		property scriptNSString : missing value
		property handlerMatches : missing value
		property handlerCount : missing value
		property handlerTextArray : missing value
		property matchIndexList : missing value
		
		on doTheBusiness(scriptText)
			set scriptNSString to |⌘|'s class "NSString"'s stringWithString:(scriptText)
			-- Get the text of all the handlers in the script and, as separate entities, the text after "end " in their last lines.
			set handlerMatchRegex to |⌘|'s class "NSRegularExpression"'s regularExpressionWithPattern:("(?ms)^[ \\t]*+(?:on (?!error)|to )([[:alnum:]_\\|]++)(?:.(?!end \\1))++.end (?-s)(\\1.*+)$") options:(0) |error|:(missing value)
			set handlerMatches to handlerMatchRegex's matchesInString:(scriptNSString) options:(0) range:({0, scriptNSString's |length|()})
			-- Build a list of the "end" labels to present to the user.
			set labelList to {}
			set handlerCount to handlerMatches's |count|()
			repeat with i from 1 to handlerCount
				set thisMatch to item i of handlerMatches
				set thisLabelRange to (thisMatch's rangeAtIndex:(2))
				set end of labelList to (i as text) & " • " & (scriptNSString's substringWithRange:(thisLabelRange)) -- Automatic coercion of NSString to text.
			end repeat
			if (labelList is {}) then display dialog "\"No handlers found in the script.\" in German!" buttons {"OK"} default button 1 cancel button 1 with icon stop
			
			-- Get the user's choice of "top" handler and start a list of handler texts with it.
			set cflResult to (choose from list labelList with prompt "Wähle Block den Ihr kopieren wollt..." default items {item 1 of labelList})
			if (cflResult is false) then error number -128
			set chosenHandlerMatchIndex to (word 1 of beginning of cflResult) as integer
			set chosenHandlerMatch to item chosenHandlerMatchIndex of handlerMatches
			set chosenHandlerText to scriptNSString's substringWithRange:(chosenHandlerMatch's range())
			set handlerTextArray to |⌘|'s class "NSMutableArray"'s arrayWithObject:(chosenHandlerText)
			set matchIndexList to {chosenHandlerMatchIndex}
			
			-- Add the texts of any other handlers called during the execution of the chosen one.
			addSubhandlers()
			
			-- Return a single text containing all the handler texts collected.
			return (handlerTextArray's componentsJoinedByString:(linefeed & linefeed)) as text
		end doTheBusiness
		
		on addSubhandlers()
			set callingHandlerText to handlerTextArray's lastObject()
			repeat with i from 1 to handlerCount
				-- Only check handlers which aren't in handlerTextArray already.
				if (i is not in matchIndexList) then
					-- Get the full text and "end" label of each.
					set thisMatch to item i of handlerMatches
					set thisHandlerText to (scriptNSString's substringWithRange:(thisMatch's range()))
					set handlerEndLabel to (scriptNSString's substringWithRange:(thisMatch's rangeAtIndex:(2)))
					-- Make up a suitable regex to identify any call to this handler in the current calling handler.
					if ((handlerEndLabel's containsString:(":")) as boolean) then
						-- Interleaved parameters. All must be in the call.
						set handlerCallRegex to (handlerEndLabel's stringByReplacingOccurrencesOfString:("[|(){}]") withString:("\\\\$0") options:(|⌘|'s NSRegularExpressionSearch) range:({0, handlerEndLabel's |length|()}))
						set handlerCallRegex to (handlerCallRegex's stringByReplacingOccurrencesOfString:(":") withString:(":(?:\"[^\"]*+\"|\\\\{[^\\\\}]*+\\\\}|\\\\([^\\\\)]*+\\\\)|\\\\|[^\\\\|]*+\\\\||[^[:space:]]++) ") options:(|⌘|'s NSRegularExpressionSearch) range:({0, handlerCallRegex's |length|()}))
						set handlerCallRegex to (handlerCallRegex's stringByReplacingOccurrencesOfString:(" $") withString:("(?:(?! )|(?= [-#(]))") options:(|⌘|'s NSRegularExpressionSearch) range:({0, handlerCallRegex's |length|()}))
					else
						-- Not interleaved parameters. Check for parenthesis in the top line of the handler.
						set handlerTopLineRange to (thisHandlerText's rangeOfString:(".+") options:(|⌘|'s NSRegularExpressionSearch) range:({0, thisHandlerText's |length|()}))
						if ((thisHandlerText's rangeOfString:("(") options:(0) range:(handlerTopLineRange))'s |length|() > 0) then
							-- Positional parameters. The handler label must be followed by parenthesis in the call.
							set handlerCallRegex to (handlerEndLabel's stringByAppendingString:("\\([^\\)]*+\\)"))
						else
							-- Labelled parameters. The handler label and the first parameter label must both be in the call.
							set handlerTopLine to (thisHandlerText's substringWithRange:(handlerTopLineRange))
							set handlerCallRegex to (handlerTopLine's stringByReplacingOccurrencesOfString:("^\\s*+(?:on|to) ([^ ]++)( [^ ]++).++") withString:("$1.*$2") options:(|⌘|'s NSRegularExpressionSearch) range:(handlerTopLineRange))
						end if
					end if
					-- Apply the regex to the text of the calling handler. If any hits, store this subhandler text and recurse to check _its_ subhandlers.
					if ((callingHandlerText's rangeOfString:(handlerCallRegex) options:(|⌘|'s NSRegularExpressionSearch) range:({0, callingHandlerText's |length|()}))'s |length|() > 0) then
						tell handlerTextArray to addObject:(thisHandlerText)
						set end of matchIndexList to i
						addSubhandlers()
					end if
				end if
			end repeat
		end addSubhandlers
	end script
	
	return o's doTheBusiness(scriptText)
end getHandlerAndSubhandlers

tell application "Script Editor" to set scriptText to text of document 2 #1
set extractedHandlersText to getHandlerAndSubhandlers(scriptText)
tell application "Script Editor" to compile (make new document with properties {text:extractedHandlersText})

@ Nigel Garvey
:cool:
Just awesome - it looks like a lot more work if done in pure AppleScript . OKk stop here, it’s just a thought and no request at all- as I’m very happy to get more ApplescriptObjC under my hands to study… Which is a step in the future. It seems to me like going back in time when I studied AppleScript for the first time :o :o

Nigel
Thanks for the asOBJc version
This is helping me to analyze my code and
See what’s being called etc.
Helping me to clean things up a bit!

I wrote here for the funs of the plain AppleScript simple but effective version. It isn’t recommended with handlers in script objects too (like with ASObjC version):


property ScripEditorName : "Script Debugger"
global allHandlers
global theHandlerNames

set {allHandlers, theHandlerNames} to {{}, {}}

-- GET HANDLERS and ITS NAMES
tell application ScripEditorName to tell document 2 to set {theHandlers, theHandlerNames} to {script handlers, name of every script handler}

-- CHOOSE THE TOP HANDLER NAME FROM LIST
set chosenHandlerName to choose from list theHandlerNames with prompt "Choose Top Handler's Name here..." default items {item 1 of theHandlerNames}
if (chosenHandlerName is false) then
	display notification "error number -128: User Cancelled"
	return
else
	set chosenHandlerName to item 1 of chosenHandlerName
end if

-- RECURSIVELY COLLECT SUBHANDLER'S NAMES
my addHandlerNames(chosenHandlerName)
return allHandlers

-- THE RECURSIVE HANDLER
on addHandlerNames(chosenHandlerName)
	tell application ScripEditorName to tell document 2 to set chosenHandlerText to source text of script handler chosenHandlerName
	if (chosenHandlerText begins with "on ") or (chosenHandlerText begins with "to") then set chosenHandlerText to paragraphs 2 thru -2 of chosenHandlerText as text
	repeat with aHandlerName in theHandlerNames
		if (((aHandlerName & "(") is in chosenHandlerText) or ((aHandlerName & ":") is in chosenHandlerText)) and (allHandlers does not contain aHandlerName) then
			set end of allHandlers to contents of aHandlerName
			my addHandlerNames(contents of aHandlerName)
		end if
	end repeat
end addHandlerNames