Script server and Library

Hi everybody

Is it possible to get a list of handlers from a scriptserver or/and library?

Thanks

Browser: Safari 531.21.10
Operating System: Mac OS X (10.6)

Hello

At least you can have a list inside the ScriptServer/Library with its handlers.

I think you could have a handlder wich decompiled at least a copy of the scriptserver, to text, so that you could
use grep or similar to get that list of handlers.

Best Regards

McUsr

Thank you very much

This is what I do now during dev, but its not very charming.
Pitty that Applescript Editor doesn’t have a way to let it execute an applescript to read that current file in the editor and add some line’s on that scpt file, or does it?

Is there a sample of this or some writings on the matter? I couldn’t find anything then just a closed applescript app or a small textual part in Applescript book from O’Reilly

Thanks again.

Hello

Try googling for “AppleScript+Decompile” but you really shouldn’t have to do that now.
I think I found an easy way to do this, thinking that that Decompile method now was a remnant of the past. That is some months ago, and I never used it, so the method I thought I found didn’t “stick” with me.

One way (with some pain) is to edit .AppleScript files (text files) and use osacompile to convert them to scripts. At least some versions of Vi, and Xcode to (I think I used Xcode to edit .AppleScript files) have color syntax support for .AppleScript and you can certainly to the same with both ScriptEditor. I believe you can do it with ScriptDebugger too.

So the options are there if you care to step one step back, and work on text files that you then compile to the library. That way you should be able to automate the generation of the handler list for the script server via shell tools.

Yeah: And I want your shell scripts when they are tested: :smiley:

Best Regards

McUsr

I think I check for the ‘Applescript editor’ service menu, and do a copy all, a handle collect action and then replace the selection with code to supply back the table.

XCode is also possible but then I have to create every time a obj-c/applescript project. Not knowing about XCode4.

Hello

It is really easy with Xcode, you are going to edit that script server for some time anyway (maybe).
At least for stuff like a script server or bigger lasting projects environments like Xcode really shines.
You can just reopen and continue where you left without further ado.

Xcode is much easier to use than you think.

You just create an empty project in Xcode under the “other” tab, nothing more difficult than that.
Then you just fill in with a new AppleScript.class, which makes a new .applescript file for you.

The IDE is also great for setting up workflows for extracting the handler names and such.

It would be cool to have your service for extracting handlers though. :wink:

Best Regards

McUsr

XCode, I use that often. But I don’t like things in my project that I don’t need.
To do things atomically, I have 15 or so scripts under the script menu.

Maybe I should create a template with just one .applescript file.

The only value I see here is that I can put it under SubVersion because its text .applescript and create a build action wich will/can do the table creation.

But on the otherhand, I use my own subversion scripts and app instead of Xcode’s, because of version differences. I save it as text from AppleScript Editor. But maybe I change tomorrow, easesly. :slight_smile:

Good Night

It is nice to have nice environments. Dedicated but not many options and jitter.

Well I used to use RCS under Tiger, it was real pain, getting the encodings right, had to deal with three different.
RCS is Ok when you are just one guy on your own project. - Otherwise I prefer Github (But Subversion is ok).

Back to RCS: With the encoding scheme under Leopard and Snow Leopard that should work like a real breeze, so I guess I’l have to fix the Tiger version, I think I can fix the scripts to work without any flaws there with what I now know, fix the script for Snow Leopard, and XCode, it would also have been great to use it in Script Debugger.

Best Regards

McUsr

a quickie

osadecompile will decompile scripts to text.

Thanks :slight_smile:

I knew I had seen something like that somewhere. How odd that I didn’t find it among the references and in the “see also” section when I perused the man pages of osascript, osalang and osacompile yesterday.

I will file that as a bug :slight_smile:

Best Regards

McUsr

I know I am quite late in this, but this may help others.

I found the original script while searching for something else and had to “modernize” it to work in today’s level of software. I have Snow Leopard. You will see the attribution in the comments for the original source of the script and about 99% of what is there now. The change I did was to use osadecompile.

It will decompile a script and list out its handlers. I am working on a means of taking this basic functionality and providing a tool for use while scripting.


property |version| : 1.0
property author : "Pescados Software · [url=http://homepage.mac.com/julifos/soft]http://homepage.mac.com/julifos/soft"[/url]
property |date| : date "Sunday, March 21, 2004 12:00:00 "
property license : "freeware, open-source"


set thisScript to choose file
set thePath to thisScript as text
set poxPath to POSIX path of thisScript
set qfPoxPath to quoted form of poxPath
stop log
set scptScript to do shell script "/usr/bin/osadecompile " & qfPoxPath & "| /usr/bin/tr -d '\\000'"
start log
set scptAlias to thisScript

set myCode to scptScript
set allHandlers to {}
set entireHandlers to {}

--> look for handlers in first line 
set firstLine to myCode's paragraph 1
set ignoreFirstChunkOn to true
set ignoreFirstChunkTo to true
if firstLine starts with "on " then
	set ignoreFirstChunkOn to false
else if firstLine starts with "to " then
	set ignoreFirstChunkTo to false
end if

--> extract "on" handlers 
set AppleScript's text item delimiters to return & "on "
set onHandlers to myCode's text items
set AppleScript's text item delimiters to {""}
if ignoreFirstChunkOn then
	set onHandlers to rest of onHandlers
else
	set item 1 of onHandlers to (text 4 thru -1 of (item 1 of onHandlers))
end if
repeat with i in onHandlers
	set i to i's first paragraph
	set x to ((offset of "(" in i) - 1)
	if x = -1 then set x to ((offset of " " in i) - 1)
	set end of allHandlers to text 1 thru x of i
	set end of entireHandlers to "on " & i
end repeat

--> extract "to" handlers 
set AppleScript's text item delimiters to return & "to "
set toHandlers to myCode's text items
set AppleScript's text item delimiters to {""}
if ignoreFirstChunkTo then
	set toHandlers to rest of toHandlers
else
	set item 1 of toHandlers to (text 4 thru -1 of (item 1 of toHandlers))
end if
repeat with i in toHandlers
	set i to i's first paragraph
	set x to ((offset of "(" in i) - 1)
	if x = -1 then set x to ((offset of " " in i) - 1)
	set end of allHandlers to text 1 thru x of i
	set end of entireHandlers to "to " & i
end repeat

-- «constant afdregfp» is "frontmost application"

--> Ok, handlers loaded
if allHandlers = {} then --> empty list
	tell application (path to «constant afdregfp» as text) to display dialog "No handlers in \"" & name of (info for scptAlias) & "\"!" buttons {"OK"} default button 1 with icon note
else
	tell application (path to «constant afdregfp» as text) to set userInput to (choose from list allHandlers with prompt "Available handlers in \"" & name of (info for scptAlias) & "\"" OK button name "To Clipboard" with multiple selections allowed and empty selection allowed)
	if userInput ≠ false and userInput ≠ {} then
		set stuffToClipboard to {}
		repeat with i in userInput
			set itemIndex to my IndexOfItem(i, allHandlers)
			set theHandler to allHandlers's item itemIndex
			set handlerEnd to "end " & theHandler
			set handlerContents to text (offset of (entireHandlers's item itemIndex) in myCode) thru ((offset of handlerEnd in myCode) + (handlerEnd's length) - 1) of myCode
			set end of stuffToClipboard to handlerContents & return
		end repeat
		set the clipboard to (text 1 thru -2 of (stuffToClipboard as string))
	end if
end if

--end listHandlers
set current_Handler to {}
set foundHandler to false
repeat with counter1 from 1 to count paragraphs of myCode
	set currentPara to paragraph counter1 of myCode
	if currentPara starts with "on" or currentPara starts with "to" then
		set x to ((offset of "(" in currentPara) - 1)
		set end of current_Handler to return & "Handler: " & (text 4 thru x of currentPara) & ": " & tab
		set foundHandler to true
	else if foundHandler is true then
		if currentPara does not start with "on" and currentPara does not start with "to" then
			repeat with counter2 from 1 to length of allHandlers
				if currentPara contains item counter2 of allHandlers and currentPara does not contain "end" then
					set end of current_Handler to item counter2 of allHandlers
				end if
			end repeat
		end if
	end if
end repeat

log current_Handler


on IndexOfItem(theItem, theList) -- credits Emmanuel Levy
	set text item delimiters to return
	set theList to return & theList & return
	set text item delimiters to {""}
	try
		-1 + (count (paragraphs of (text 1 thru (offset of (return & theItem & return) in theList) of theList)))
	on error
		0
	end try
end IndexOfItem

Hello.

Here is a humble contribution to that script, it is really made by Nigel Garvey.

If you use this instead of the “dreaded date literal” then It can compile everywhere without any editing.


on setDateAndTime(intYear, intMonth, intDay, int23Hour, int59Minutes, int59Seconds) -- fixed by Nigel Garvey
   tell (current date)
       set {day, year, its month, day, time} to {1, intYear, intMonth, intDay, int23Hour * hours + int59Minutes * minutes + int59Seconds}
       return it
   end tell
end setDateAndTime

The date property looks so much better this way: :slight_smile:


property |date| : ""
” first in the implicit run handler:
set |date|  to  setDateAndTime(2004,3,21,12,0,0)

Edit: How about adding it to my ScriptLIbrary loader?, It would be neat to have all the handlers from
library files into a database events database. I event think or are eager to find out if you can store a file alias in that database, -(the HFS id of the file). (That would be nice) only thing one had to do was some folder actions to update the contents of the database when items are added to and deleted from the library folder. And a cron job or similar to check for changed files.

And some LIbraries comes with documentation. It would have been nice to have access to that documentation from within the script Editor, by some smaller Safari window.

Think about it, and please test the ScriptLibraryLoader in the mean time.

It is by no means finished yet, but it does what it does in reasonable manner.

Hello

This is a worthy case for some scripting . :slight_smile: It is not straight forward to get right. First of all: you can have handlers within script objects and script objects within script objects with handlers within which can contain script objects which can contain handlers which can contain . -That was a little bit off, but you get what I mean.

One can’t handle all situations. For starters it should suffice to list every handler which is either declared at the top level, together with the top level script objects and their handlers (indented). in a list.

It should be possible to get to the direct position of a wished for handler in that file in an editor.
It should also be possible to paste the full declaration of the handlers signature to the clipboard.

This is not that a heavy weight process, but it takes its toll. the best thing would have it stored somewhere, so it just can look it up, and keep library files locked, until they are to be edited, not so much to ensure their contents, more to ensure that unnecessary file updating don’t take place.

I have played a little bit with Julio’s script, and it doesn’t work that well, when it comes to script servers and the like, where one usually would want to have several script objects containing handlers, due to the fact that the handlers are indented by tabs.

”>What do we do if we have one single script object in a file containing everything?
” I suggest we leave that script object on the outside of the list, together with the filename.

If there are several script objects, then a tree structured view by indentation of a list box would be more appropriate. Maybe it would be convenient with a contracted view as well, with only the script object, if the scriptLibrary is a huge server.

For starters it would be nice to just come up with an “on the fly” solution, which tackles the top level entities and
any handlers within those entities, which should be challenging enough in its own right. So challenging given the tools applescript provides us, so that I will take “a stroll” and have a look for some solutions that may exist within the AppleScript community.

There would nee to be a database created on the fly with records containing… Type (Script, handler), Name(…), start paragraph(…), end paragraph(…), and possibly “parent” or “contained by” and then an associated list that includes what handlers or scripts this is referenced in. I would love to have the time then to set the script to automatically output this to OmniGraffle to draw a representation of the structure of a script. Would be useful for complex ones.

Anyway, I will craft up a draft script using the database and try it. Should be able to have recursive routines to drill down as far as needed to get scripts and or handlers.

John

Hello.

Yes, OmniGraffle would be it, but then it wouldn’t be for everybody, I hadn’t thought of that yet. But if you generate output for Omni Graffle,then you could also generate for dot and graphwiz, which are free - not alternatives to Omni Graffle (I own it - and it’s great.)

I was hoping to avoid writing a full blown parser in Apple Script :smiley: So I’m more in for stripping all comments,
parsing the the top level handlers and script objects, then extracting handler within each script object, and see how usable that is, or what I want once I have gotten it that far.

I basically want to be able to paste in handler signatures into my scripts. There would also be nice to be able to get up a documentation page for a script object, but that can wait.

When it eventually comes to database events, then I at least want to have one or two categories with each script object, the containing file, (I think relations is a rather manual affaire in Database Events :slight_smile: But I still want one file with file relevant info, and such. If a handler isn’t contained within a script object, then the file is the script object.
-And that is an ambiguity.

I’ll restrain myself, and check out how easy it is to build that cheap prototype on - the fly solution, which may or may not be usable.

Here is rev 5 (edited over 3 & 4).
Rev 5: added branch processing (it has to be worked on as duplicate branches show up in complex scripts)
Rev 4 adds timing and lines per sec calc for those interested.
Rev 3: Includes some bug fixes I ran into… flagged in the comments where done
Includes a handler to strip all comments that are mixed on lines with code, including the start of multi-line comments that start on a line of code. Is ‘smart enough’ to understand double quotes to ensure no quoted comment flags are detected.
The code now includes the To Do list as well, which can be tracked to see what is to be done.


-- Based on the initial concept of "Pescados Software · [url=http://homepage.mac.com/julifos/soft]http://homepage.mac.com/julifos/soft"[/url] in "explore Handlers"
(*
Work to be done...
1. Be able to make a map of the processes and their inter-reationships
2. Tracking and listing of Handlers and scripts in Libraries referenced under Tell blocks
3. Possible output to OmniGraffle to get a drawing of the handler/script layout map
4. Look into merging the comment elimination handlers (full line and partial line comments)

Changes versus rev 4
1. Added logic to ensure full line comments eliminated if in-line comment elimination is wanted
2. Misc comments added regaring passing information at the end of processing to another script or application
3. Added length variables for databases
4. One database added, contains the data based on the 'containing' routine, line number of instance, contained routine
5. Added searchForBranches handler to get all routine branches (delivers some duplicates that need to be addessed when handlers are cross-referenced and are in scripts that are in handlers) NOTE: this slows down the processing depending on number of routines and branches

Changes versus rev 3
1. Eliminated out of date comments
2. Modified attribution to reduce potential for confusion between scripts
3. Added timing to the script with a report to log. Note I get a crash using the timing script as set up by McUsr, see my work around
4. Made 'log of script stripped of all comments' a comment. This could be used to ship uncommented code out, if wanted
Changes versus rev 2
1. Bug fixes as flagged
2. Added handler to get rid of any comments at the end of lines, requires the eliminate comments value must be set to true
*)
------------------------------------------------------------------------------------------------

global HandlerDatabase
global ScriptDatabase
global CommentsDatabase
global EliminateCommentLines -- changed the name in rev 3 to this from EliminateComments
global EliminateCodeLineComments --  added rev 3 to get comments at end of lines of code
global ContainerDatabase -- added rev 5 to get the list of handlers branched form handlers...
global GetBranches
----------
-- TIMING Script
script timeTools
	-- © McUsr 2010
	--    property parent : AppleScript
	property getMillisec : missing value
	on firstMillisec()
		local overhead
		my realgetMillisec() -- gets stuff loaded into mem for later, this first call takes longer time.
		set my getMillisec to my realgetMillisec
		set _overhead to -(my realgetMillisec()) + 2 * (my realgetMillisec()) -- Nigel Garvey
		return _overhead
	end firstMillisec
	on realgetMillisec()
		local res
		set res to do shell script "/Library/UnixApps/timetools -ums"
		-- you must change path to your hardcoded path to timetools,
		return res
	end realgetMillisec
	--on run
	set my getMillisec to my realgetMillisec -- changed versus McUsr original (was: firstMillisec)
	--end run
end script

------  items to modify to adjust results, could be set via dialogs
set EliminateCommentLines to true --	include comments increases procesing, try it, returns a list of all comments, the longer=slower
set EliminateCodeLineComments to true -- eliminates in-line comments that share a line with code
set GetBranches to true -- find referenced branches to handlers and scripts inside handlers and scripts

if GetBranches is true then
	set EliminateCommentLines to true -- need all comments turned into blanks as a reference in a comment may cause a false match
	set EliminateCodeLineComments to true
end if

------
set thisScript to choose file --  get the script to check
set poxPath to POSIX path of thisScript
set qfPoxPath to quoted form of poxPath
stop log
set ScrptCode to do shell script "/usr/bin/osadecompile " & qfPoxPath & "| /usr/bin/tr -d '\\000'" --	gets script and decompiles
start log
----------
set HandlerDatabase to {}
set ScriptDatabase to {}
set CommentsDatabase to {}
set db3 to {}
set theCleanScript to ""
----------
-- HandlerDatabase: Handlers- theName:, Start:, Finish:, ContainedBy:
-- ScriptDatabase: Scripts- theName:, Start:, Finish:, ContainedBy:
-- CommentsDatabase: Lines as comments- Start:, Finish, Contents:
-- ContainerDatabase: Referenced entities- theContainer, theLine, theBranch, theType -- where a handler has code that branches to another handler, done rev 5
----------
-- T I M I N G C O D E
tell timeTools to run
set t to timeTools's getMillisec()
set StartTime to t

-----------------------------------  this is the list of handlers used to process the file and parse the results
set theCodeLength to count paragraphs of ScrptCode
set theCleanScript to EliminateTabs(theCodeLength, ScrptCode) -- gets rid of leading tabs

set theCleanScript to ProcessCommentLines(theCodeLength, theCleanScript) -- do comment lines first
if EliminateCodeLineComments is true and EliminateCommentLines is true then
	set theCleanScript to ProcessCodeLineComments(theCodeLength, theCleanScript)
	--  do comments at end of lines, requires full line comments be eliminated first
end if

set counter1 to 1 -- tracks the paragraph being analyzed
ProcessScrptsHandlrs(theCleanScript, counter1, theCodeLength, "Implied Run")
--  create databases of handlers & scripts, implied run means at top level and no 'on run' handler

set countHandlers to length of HandlerDatabase
set countScripts to length of ScriptDatabase

if GetBranches is true then
	searchForBranches(theCleanScript, theCodeLength, countHandlers, countScripts)
end if

-- -------  T I M I N G C O D E
tell timeTools to run
set t to timeTools's getMillisec()
set EndTime to t
set theTimeResult to (EndTime - StartTime) / 1000
------------
-- Added this to provide a review of results, of course these could be passed to other scripts...
if EliminateCommentLines is false then
	log "-- Comments --"
	log "-- count: " & length of CommentsDatabase
	log CommentsDatabase
end if
log "-o-"
log "Time to complete: " & theTimeResult & " seconds"
log "Lines of code/comments analyzed: " & theCodeLength
log "Lines/sec: " & theCodeLength / theTimeResult
log "-o-"
log "-- Handlers -- "
log "-- count: " & countHandlers
log HandlerDatabase
log "-- Scripts -- "
log "-- count: " & countScripts
log ScriptDatabase
if GetBranches is true then
	log "Containers"
	log ContainerDatabase
end if

----------
on EliminateTabs(theCodeLength, ScrptCode) --  find the tabs within the code and eliminate them
	local ParaCounter
	set ParaCounter to 1
	set theCleanScript to ""
	repeat -- this is to strip out tabs and, if eliminate comments is true, to make blanks of any lines that are space or tab character
		set PrecursorText to paragraph ParaCounter of ScrptCode
		repeat
			if PrecursorText ≠ "" and PrecursorText ≠ " " and PrecursorText ≠ "	" then
				if character 1 of PrecursorText = tab then
					set PrecursorText to (characters 2 through -1 of PrecursorText) as string
				else
					if PrecursorText starts with "on error" then set PrecursorText to "xx error"
					set theCleanScript to theCleanScript & PrecursorText & return
					exit repeat
				end if
			else
				if EliminateCommentLines is true and (PrecursorText = " " or PrecursorText = "	") then --fixed ≠ to =
					set PrecursorText to ""
					set theCleanScript to theCleanScript & PrecursorText & return
					exit repeat
				end if
				if PrecursorText starts with "on error" then set PrecursorText to "xx error"
				set theCleanScript to theCleanScript & PrecursorText & return
				exit repeat
			end if
		end repeat
		set ParaCounter to ParaCounter + 1
		if ParaCounter > theCodeLength then exit repeat
	end repeat
	return theCleanScript
end EliminateTabs

----------
on ProcessCommentLines(theCodeLength, theCleanScript) -- and eliminate if flag set
	local ParaCounter
	--  find the comment lines within the code
	set ParaCounter to 1
	set ScrptCode to ""
	repeat -- parse for comments, then make blanks if eliminate comments is true
		if paragraph ParaCounter of theCleanScript starts with "--" then
			saveDBRecord(ParaCounter, ParaCounter, (paragraph ParaCounter of theCleanScript)) --Params: Start line, Finish line, Comment
			set ScrptCode to CommentElimination(ParaCounter, ScrptCode, theCleanScript) --  see if comments are blanked, then save-- fixed as was missing from rev. 2
		else if paragraph ParaCounter of theCleanScript starts with "#" then
			saveDBRecord(ParaCounter, ParaCounter, (paragraph ParaCounter of theCleanScript))
			set ScrptCode to CommentElimination(ParaCounter, ScrptCode, theCleanScript) --  see if comments are blanked, then save
		else if paragraph ParaCounter of theCleanScript starts with "(*" then
			if paragraph ParaCounter of theCleanScript contains "*)" then
				saveDBRecord(ParaCounter, ParaCounter, (paragraph ParaCounter of theCleanScript))
				set ScrptCode to CommentElimination(ParaCounter, ScrptCode, theCleanScript) --  see if comments are blanked, then save
			else
				set StartLine to ParaCounter
				set TempComment to paragraph ParaCounter of theCleanScript & return
				set ScrptCode to CommentElimination(ParaCounter, ScrptCode, theCleanScript) --  see if comments are blanked, then save
				repeat with counter1 from (ParaCounter + 1) to theCodeLength
					if paragraph counter1 of theCleanScript contains "*)" then
						set TempComment to TempComment & paragraph counter1 of theCleanScript & return
						saveDBRecord(StartLine, counter1, TempComment)
						set ParaCounter to counter1
						set ScrptCode to CommentElimination(ParaCounter, ScrptCode, theCleanScript) --  see if comments are blanked, then save
						exit repeat
					else --  the multi-line comment continues
						set TempComment to TempComment & paragraph counter1 of theCleanScript & return
						set ScrptCode to CommentElimination(ParaCounter, ScrptCode, theCleanScript) --  see if comments are blanked, then save
					end if
				end repeat
			end if
		else
			set ScrptCode to ScrptCode & paragraph ParaCounter of theCleanScript & return
		end if
		if ParaCounter = theCodeLength then exit repeat
		set ParaCounter to ParaCounter + 1
	end repeat
	set theCleanScript to ScrptCode -- was missing in rev 2, added to speed results when comments eliminated 
	(*  Code for use when debugging
	if EliminateCommentLines is true then
		log "Code without tabs and full line comments: " & theCleanScript
	else
		log "Code without tabs but retaining full line comments: " & theCleanScript
	end if
	*)
	return theCleanScript
end ProcessCommentLines

----------
on ProcessCodeLineComments(theCodeLength, theCleanScript)
	--  this will find and eliminate any comments at the end of lines of code
	local ParaCounter
	--  find the comments within the code and elimiinate
	-- lines of comments must be blanked by this point
	set ParaCounter to 1
	set NewScript to ""
	stop log -- skips bogus log entry for the ASCII value find, start it if wanted for debugging
	repeat
		set QuoteActive to false
		set CommentActive to false
		set MultiLineActive to false
		set theActualCode to paragraph ParaCounter of theCleanScript
		set TempComment to ""
		--  only scan text that may have a comment, notice no need to look for end of multi-line comment as done later
		if theActualCode contains "#" or theActualCode contains "--" or theActualCode contains "(*" then --skip if no comments
			repeat with counter1 from 1 to ((length of theActualCode) - 1)
				set item1 to character counter1 of theActualCode
				if item1 = (ASCII character (34)) then -- if this is a set of quoted text then wait for it to end
					if QuoteActive is false and CommentActive is false and MultiLineActive is false then
						set QuoteActive to true
					else
						set QuoteActive to false
						-- like a semaphore on/off switch to detect when there is quoted text that may include a comment flag inside of it
					end if
				end if
				if QuoteActive is false then
					if item1 = "#" then
						if MultiLineActive is false then
							set theActualCode to characters 1 thru (counter1 - 1) of paragraph ParaCounter of theCleanScript as string
							exit repeat
						end if
					else if item1 = "-" or item1 = "*" or item1 = "(" then
						if counter1 ≤ ((length of theActualCode) - 1) then
							set item2 to characters counter1 thru (counter1 + 1) of theActualCode as string
							if item2 = "--" then
								if MultiLineActive is false then
									set CommentActive to true
									set theActualCode to characters 1 thru (counter1 - 1) of paragraph ParaCounter of theCleanScript as string
									exit repeat
								end if
							else if item2 = "*)" then
								error number 0 --  should never get here
							else if item2 = "(*" then
								set CommentActive to true
								set MultiLineActive to true
								set theActualCode to characters 1 thru (counter1 - 1) of paragraph ParaCounter of theCleanScript as string
								repeat with counter2 from (ParaCounter + 1) to theCodeLength
									if paragraph counter2 of theCleanScript contains "*)" then
										set TempComment to TempComment & "" & return
										set ParaCounter to counter2
										exit repeat
									else --  the multi-line comment continues
										set TempComment to TempComment & "" & return
									end if
								end repeat
								exit repeat
							end if
						end if
					end if
				end if
			end repeat
		end if
		set NewScript to NewScript & theActualCode & return & TempComment
		if ParaCounter < theCodeLength then
			set ParaCounter to ParaCounter + 1
		else
			--log NewScript
			--  turn this on to see the script stripped of all comments in the log (you may like to use this version of the script)
			exit repeat
		end if
	end repeat
	start log
	return NewScript -- script now cleansed of all comments
end ProcessCodeLineComments

----------
on ProcessScrptsHandlrs(theCleanScript, counter1, theCodeLength, ContainingRoutine)
	-- find the handlers and scripts within the code
	repeat
		if EliminateCommentLines is false then
			repeat with counter2 from 1 to length of CommentsDatabase
				if counter1 ≥ (Start of (item counter2 of CommentsDatabase)) and counter1 ≤ (Finish of (item counter2 of CommentsDatabase)) then
					-- this is inside a comment, so skip this counter1 paragraph
					exit repeat
				end if
				-- this is for post comment verification
				set counter1 to DetermineRoutine(theCleanScript, counter1, theCodeLength, counter2, ContainingRoutine)
			end repeat
		else -- commented lines have been converted to blanks
			set counter1 to DetermineRoutine(theCleanScript, counter1, theCodeLength, 1, ContainingRoutine)
		end if
		if counter1 = theCodeLength then
			exit repeat
		else
			set counter1 to counter1 + 1
		end if
	end repeat
end ProcessScrptsHandlrs

------
on handlerTracker(theCleanScript, lineID, theCodeLength, CommentsDone, ContainerHandler)
	local counter1, FoundEnd, db1Record
	set db1Record to {theName:"", Start:"", Finish:"", ContainedBy:""}
	set textLine to paragraph lineID of theCleanScript
	set HandlerName to word 2 of textLine --as string -- leaves the 'on' and 'to' off and gets handler name
	set theName of db1Record to HandlerName --  the handler's name
	set db1Record's Start to lineID
	set db1Record's ContainedBy to ContainerHandler
	set FoundEnd to false
	set counter1 to lineID + 1
	repeat
		if EliminateCommentLines is false then
			repeat with counter2 from CommentsDone to length of CommentsDatabase
				if counter1 ≥ (Start of (item counter2 of CommentsDatabase)) as integer and counter1 ≤ (Finish of (item counter2 of CommentsDatabase)) as integer then
					-- this is inside a comment, so skip this counter1 paragraph
					exit repeat
				end if
				if paragraph counter1 of theCleanScript starts with "script" then
					--  this starts a script, send to script tracker
					set counter1 to scriptTracker(theCleanScript, counter1, theCodeLength, counter2, HandlerName)
				else if paragraph counter1 of theCleanScript starts with "On" then
					--  this starts a handler, send to the handler tracker
					set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, counter2, HandlerName)
				else if paragraph counter1 of theCleanScript starts with "To" then
					--  this starts a handler, send to the handler tracker
					set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, counter2, HandlerName)
				else if paragraph counter1 of theCleanScript starts with "end " & db1Record's theName then
					set db1Record's Finish to counter1
					set FoundEnd to true
					exit repeat
				end if
			end repeat
		else --Comments have been made blanks
			if paragraph counter1 of theCleanScript starts with "script" then
				--  this starts a script, send to script tracker
				set counter1 to scriptTracker(theCleanScript, counter1, theCodeLength, 1, HandlerName)
			else if paragraph counter1 of theCleanScript starts with "On" then
				--  this starts a handler, send to the handler tracker
				set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, 1, HandlerName)
			else if paragraph counter1 of theCleanScript starts with "To" then
				--  this starts a handler, send to the handler tracker
				set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, 1, HandlerName)
			else if paragraph counter1 of theCleanScript starts with "end " & db1Record's theName then
				set db1Record's Finish to counter1
				set FoundEnd to true
				exit repeat
			end if
		end if
		if FoundEnd is true then exit repeat
		if counter1 = theCodeLength then
			exit repeat
		else
			set counter1 to counter1 + 1
		end if
	end repeat
	set the end of HandlerDatabase to db1Record
	return counter1
end handlerTracker

------
on scriptTracker(theCleanScript, lineID, theCodeLength, CommentsDone, ContainerHandler)
	local counter1, FoundEnd, db3Record
	set db3Record to {theName:"", Start:"", Finish:"", ContainedBy:""}
	set textLine to paragraph lineID of theCleanScript
	set ScriptName to word 2 of textLine --as string -- leaves the 'on' and 'to' off and gets handler name
	set theName of db3Record to ScriptName --  the handler's name
	set db3Record's Start to lineID
	set db3Record's ContainedBy to ContainerHandler
	set FoundEnd to false
	set counter1 to lineID + 1
	repeat
		if EliminateCommentLines is false then
			repeat with counter2 from CommentsDone to length of CommentsDatabase
				if counter1 ≥ (Start of (item counter2 of CommentsDatabase)) as integer and counter1 ≤ (Finish of (item counter2 of CommentsDatabase)) as integer then
					-- this is inside a comment, so skip this counter1 paragraph
					exit repeat
				end if
				if paragraph counter1 of theCleanScript starts with "Script" then
					--  this starts a script, send to script tracker
					set counter1 to scriptTracker(theCleanScript, counter1, theCodeLength, counter2, ScriptName)
				else if paragraph counter1 of theCleanScript starts with "On" then
					--  this starts a handler, send to the handler tracker
					set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, counter2, ScriptName)
				else if paragraph counter1 of theCleanScript starts with "To" then
					--  this starts a handler, send to the handler tracker
					set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, counter2, ScriptName)
				else if paragraph counter1 of theCleanScript starts with "end script" then
					set db3Record's Finish to counter1
					set FoundEnd to true
					exit repeat
				end if
			end repeat
		else --Comments have been made blanks
			if paragraph counter1 of theCleanScript starts with "Script" then
				--  this starts a script, send to script tracker
				set counter1 to scriptTracker(theCleanScript, counter1, theCodeLength, 1, ScriptName)
			else if paragraph counter1 of theCleanScript starts with "On" then
				--  this starts a handler, send to the handler tracker
				set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, 1, ScriptName)
			else if paragraph counter1 of theCleanScript starts with "To" then
				--  this starts a handler, send to the handler tracker
				set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, 1, ScriptName)
			else if paragraph counter1 of theCleanScript starts with "end script" then
				set db3Record's Finish to counter1
				set FoundEnd to true
				exit repeat
			end if
		end if
		if FoundEnd is true then exit repeat
		if counter1 = theCodeLength then
			exit repeat
		else
			set counter1 to counter1 + 1
		end if
	end repeat
	set the end of ScriptDatabase to db3Record
	return counter1
end scriptTracker

----------
-- there are duplicate branch entries being found that need to be resolved

----------
--  Looking for branches to handlers and scripts within handlers/scripts
on searchForBranches(theCleanScript, theCodeLength, countHandlers, countScripts)
	--  uses the globals: handlerdatabase and scriptdatabase for info on handler and script locations
	set ContainerDatabase to {}
	if countHandlers > 0 then
		repeat with HandlerCounter1 from 1 to countHandlers
			repeat with HandlerCounter2 from 1 to countHandlers
				repeat with counter1 from ((Start of (item HandlerCounter2 of HandlerDatabase)) + 1) to ((Finish of (item HandlerCounter2 of HandlerDatabase)) - 1) -- Start+1 due to need to skip the first line of routine ('on Handler()'), finish -1 for similar reason
					if paragraph counter1 of theCleanScript contains ((theName of (item HandlerCounter1 of HandlerDatabase)) as text item) and paragraph counter1 of theCleanScript does not contain (((theName of (item HandlerCounter1 of HandlerDatabase)) as text item) & "'s") then
						set db4Record to {theContainer:theName of (item HandlerCounter2 of HandlerDatabase), theLine:counter1, theBranch:theName of (item HandlerCounter1 of HandlerDatabase), theType:"HinH"}
						set the end of ContainerDatabase to db4Record
					end if
				end repeat
			end repeat
			if countScripts > 0 then
				repeat with ScriptCounter2 from 1 to countScripts
					repeat with counter1 from ((Start of (item ScriptCounter2 of ScriptDatabase)) + 1) to ((Finish of (item ScriptCounter2 of ScriptDatabase)) - 1) -- Start+1 due to need to skip the first line of routine ('on Handler()'), finish -1 for similar reason
						if paragraph counter1 of theCleanScript contains ((theName of (item HandlerCounter1 of HandlerDatabase)) as text item) and paragraph counter1 of theCleanScript does not contain (((theName of (item HandlerCounter1 of HandlerDatabase)) as text item) & "'s") then
							set db4Record to {theContainer:theName of (item ScriptCounter2 of ScriptDatabase), theLine:counter1, theBranch:theName of (item HandlerCounter1 of HandlerDatabase), theType:"HinS"}
							set the end of ContainerDatabase to db4Record
						end if
					end repeat
				end repeat
			end if
		end repeat
	end if
	if countScripts > 0 then
		repeat with ScriptCounter1 from 1 to countScripts
			repeat with HandlerCounter2 from 1 to countHandlers
				repeat with counter1 from ((Start of (item HandlerCounter2 of HandlerDatabase)) + 1) to ((Finish of (item HandlerCounter2 of HandlerDatabase)) - 1) -- Start+1 due to need to skip the first line of routine ('on Handler()'), finish -1 for similar reason
					if paragraph counter1 of theCleanScript contains ((theName of (item ScriptCounter1 of ScriptDatabase)) as text item) and paragraph counter1 of theCleanScript does not contain (((theName of (item ScriptCounter1 of ScriptDatabase)) as text item) & "'s") then
						set db4Record to {theContainer:theName of (item HandlerCounter2 of HandlerDatabase), theLine:counter1, theBranch:theName of (item ScriptCounter1 of ScriptDatabase), theType:"SinH"}
						set the end of ContainerDatabase to db4Record
					end if
				end repeat
			end repeat
			if countHandlers > 0 then
				repeat with ScriptCounter2 from 1 to countScripts
					repeat with counter1 from ((Start of (item ScriptCounter2 of ScriptDatabase)) + 1) to ((Finish of (item ScriptCounter2 of ScriptDatabase)) - 1) -- Start+1 due to need to skip the first line of routine ('on Handler()'), finish -1 for similar reason
						if paragraph counter1 of theCleanScript contains ((theName of (item ScriptCounter1 of ScriptDatabase)) as text item) and paragraph counter1 of theCleanScript does not contain (((theName of (item ScriptCounter1 of ScriptDatabase)) as text item) & "'s") then
							set db4Record to {theContainer:theName of (item ScriptCounter2 of ScriptDatabase), theLine:counter1, theBranch:theName of (item ScriptCounter1 of ScriptDatabase), theType:"SinS"}
							set the end of ContainerDatabase to db4Record
						end if
					end repeat
				end repeat
			end if
		end repeat
	end if
end searchForBranches

--  ==========  Handler to place comments into database
on saveDBRecord(StartValue, FinishValue, CommentValue)
	set db2Record to {Start:StartValue, Finish:FinishValue, Comment:CommentValue}
	set end of CommentsDatabase to db2Record
end saveDBRecord

--  ==========  Handler to check and then blank comments but retain lines
on CommentElimination(ParaCounter, ScrptCode, theCleanScript)
	if EliminateCommentLines is true then
		set ScrptCode to ScrptCode & "" & return
	else
		set ScrptCode to ScrptCode & paragraph ParaCounter of theCleanScript & return
	end if
	return ScrptCode
end CommentElimination

--  ==========  Handler determines if code is a script or handler start
on DetermineRoutine(theCleanScript, counter1, theCodeLength, counter2, ContainingRoutine)
	ignoring case
		if paragraph counter1 of theCleanScript starts with "On" or paragraph counter1 of theCleanScript starts with "To" then
			--  this starts a handler, send to the handler tracker
			set counter1 to handlerTracker(theCleanScript, counter1, theCodeLength, counter2, ContainingRoutine)
		else if paragraph counter1 of theCleanScript starts with "script" then
			--  this starts a script, send to script tracker
			set counter1 to scriptTracker(theCleanScript, counter1, theCodeLength, counter2, ContainingRoutine)
		end if
	end ignoring
	return counter1
end DetermineRoutine


Model: MacBook (early 2009)
Browser: Safari 533.16
Operating System: Mac OS X (10.6)

Hello.

I’m sitting here and working on my version, it is hopefully soon bug free.
I won’t look at yours before mine is ready!
(But then I will look closely.) :slight_smile:

Here it comes.

You better check this post once and twice, because I’m not finished with the description and hows and whys and such of it. Yet I have to go get some food :).

Great Thanks to oldmanegan for triggering me into this and to Julio for always being a guiding star. :slight_smile:

My version handles any (so far) library. No matter how they are organized.

¢	It returns a list of the entities hierarchically in order to give you the overall structure.
	- Handlers and Script objects that contains either handlers or script objects.

¢	It removes what I consider as implementation details as empty script objects - "o"

It doesn’t do much more than that at the moment, but I will soon start to add some features. -I have just cleaned up the code and want to play a little with it. But it gives a good overview of what a library contains. -And on my machine it runs fast enough to be used on the fly. I realize that this is not necessarily the case on all machines, and will optimize it. I have used Satimage.osax’s regexp package in order to create it, so you need to download that to test it. I have all position data intact from Satimage’s find text, but I haven’t collected any end position data, nor comments.
Read more about this later on.

I will include it in the library loader of mine.

Here comes the “proper text for the code” which aren’t edited to a finished state yet.
Edit Yours and mine are about the same length.

What remains to be done

1. At the moment the LibraryLister only deals with Library files, and not script servers, I will come back to that later on, as that parsing of a ScriptServer Applet is really a separate task. What I mean by script server is having an applet, which loads script libraries either on run or on demand.

2. I can’ generate output in “dot” for graphwiz of omnigraffle yet, as I haven’t collected or generated any containment information, but I will have this as a side task while I optimize.

3. Optimize it for what its worth.

4. Apply a sensible set on operations on the list, and such, I have think through this, and play with it first.

HOW AND WHY THIS THING WORKS AS IT DOES

The Objective

A parser that works with any kind of scripted Library/As file.

To generate a list of all the entities, but no more than those that are
interesting or should be interesting for a user of a Library. No matter how the
Library looks like or are organized, it should allways work properly and give
the desired result.

Any implementation details, like script objects without handlers, or properties
are washed away together with any handlers that are commented out. We are
dealing with the working code here. -If I want to know about these things I
figure I will open the source and have a gander anyway.

This is a rather sluggish implementation, where the main objective was to have
something that rather worked, than worked fast. There are wast opportunities for
optimization, which I will return to when I’m sure that I have got it “right”.

Comments in the form of handler descriptions is a feature I haven’t had in
mind during this phase of development
as this is a rather big and unexplored
area at the moment with a phletoria of differing ways to comment scripts.

Showing dependencies among script objects should also be relatively easy to add.

Any thoughts of any form of database implementation is also lacking, but most of
the necessary data are retained in records in the final list before we generate
the textual output which is my main interest at the moment. (Playing)

This is rather a prototype, where thing at the moment are generated on the fly,
so that I can play with it. (I love to play!) Maybe I’ll discover something, or
get some new I ideas before I go further.

Foundation

This things uses the formatting of a compiled applescript as
the foundation for figuring out the hierarchy of the various entities* That is
the number of tabs that prepends an entity -Relative to the former level.
*(I regard either a Script Object or a handler as an entity in this context).
that is: you can allways figure out that a handler or script object is within
another handler or script object by it’s indentation, so I therefore needen’t to
consider the ending of any entities. It is also a fact that Script Libraries
and other AppleScript files this LibrarLister produces output for
has had to be compiled, before source text could end up here, which frees the
script for any considerations about correct syntax and structure.

Strategy for solving this task

The very first thing it does after having received the source text is to surveil all
the block comment positions. As we will wash out anything from within those.

I collect all top level entities first, since this is a level in its own right.

This level is also special in that this is the only place where handlers reside
side by side with script objects; I have chosen this view out of convenience,
because I then will never have to classify handlers and script objects, but can
allways regard them as entites and never being afraid of washing out any
handlers with the empty script objects.

This is really a special case of the general handler, since we will merge any
top lever handlers as a final step. Thereby being able to both retain the
handlers in the final product and at the same time been enabled to wash out
empty script objects without having to classify each and everyone of the
entities at the moment.

We then recurse down the wanted levels of the object hierarchy of the source
text. At the moment I feel that it is enough to go down and collect any
eventual handlers of the 5 level. We have covered the first level in our run
handler, so there is a depth of four more levels to cover, before we do the
final washing of any empty first level script objects, and merges this clean
list with both the handlers from the top level and the resulting clean list from
the recursive collector routine.

Summary

I have a tool that can visualize the contents of a single script library and that’s
about it. All data except end markers for the entities are present in the parsing
process. The end markers are Very easy to get. No operations on the
displayed list are so far implemented, nor any database solution for storing
parsed files. I will implement some basic features, forAs Editor, Script
Debugger and Smile
and play a little with those. I also see the possibilites of
using it as an on the fly “Annotation tool” while scripting big scripts, and it is
usable for that as well, but I foresee it needs some optimization for comfortable
usage on old machines. I’m seriously considering to implement it as a stand
alone server for my ScriptLibraryLoader. And use it as a
Stand alone tool. I never enjoyed running scripts with a size of 1000 lines on my
old G4 powerbook. :frowning: Speaking of: I will make it Tiger compatible with help by
Satimage/Smile for Tiger, which supports decompiling.

I will ponder constructing any database for a while as I really won’t spend time
implementing something before I fully understand my needs

A preliminary Comparison

We have headed in two different directions, where I have been focused on one thing
, and that has been to be able to parse everything and display it correctly. My
solution is doing far less than yours, which does everything, and deeper.

-I haven’t tested your’s yet, but I assume it will display everything correctly.
We both ended up with recursive routines. But here our solutions grossly differ.
I refuted to write a regular parser for starters; I have had some trouble with
that approach when it has come to presenting data earlier.

I have had me as an end user of a library in mind when I wrote, I have no or
should have no interest in implementation details I use.

But you have done this and even more, with nicely concerning comments, where I
have neglected them, you may even support cross referencing and ToDo’s!.

Your solution, is more for the Library builder with cross referencing and all
that, -all in all a complete tool. My tool is on the other hand easy to combine
with other tools to build bigger tools. And that is the path I’m going to take.

I haven’t tested your tool, nor timed it, but I will. Any comparision will, if
the favour mine, be unfair to you, since yours do so much more.

Here is the output of the Script when run on itself


run
getLibraryText(aRefToPxFile)
getScriptObjectEntities(tabIndent, theTextToPeruse, commentlist, callCount, MAXLEVEL)
mergeHandlersFindPossibleEmptyScriptObjects(HandlerList, ScriptObjectList, MergedList, washingCandidates, callCount, MAXLEVEL)
mergeCleanLists(parentList, childList)
washParentListForEmptyObjects(parentList, childList, origScrObjList, washList)
washMergeListForEmptyScriptObjects(mergeList, washList)
washOutBlockCommentedEntities(entityList, blockCommentList)
washOutOnErrorStatements(HandlerList)
makeEntityList(seachString, searchItemNr, theTextToPeruse)
prependIndent(tabIndent, HandlerList)
withinABlockComment(aPos, listOfBlockPos)
getBlockCommentPositions(theTextToPeruse, listOfBlockPos)
indexByNextAList(listToIndex)

Here is the output when run on Julio’s sfri Librariy


application "Safari"
	modifyimage(imgID, |attribute|, newvalue)
	sendcookie(nombre, valor, caduca)
	currentcookies()
	writedata(t)
	openwindow given data:{x, y, Z, a}
	setcolor(theelement, thecolor)
	getcolor(theelement)
	progressBar
		endBar()
		updateBar()
		initBar()
		resetBar()
	Formularios
		setvalue()
		focus()
		blur()
		select
		submit()
		reset()
		listforms()
		wathvalue(fID, eID)
		StringToList(k, tid)
	statistics with allinfo
	displayconfirm(msg)
	displayprompt(msg, ans)
	loadpage(wheretogo)
	location(frame, theloc)
	fullscreen()
	resizewin to {w, H, centered}
	movewin to {x, y}
	scrollwin to {x, y}
	focuswin(docObject)
	aboutmonitor()
	aboutnavigator()
	aboutplugins()
	aboutmimetype(num)
	aboutimage(imgID)
	aboutme()
	extractBookmarks()
	extractImages()
	extractLinks()
	ExtractObjects(obj, v)
	ExtractObjects2(obj, v)
	returnprashes(tid, off, txt)
	SearchReplace(s, r, theText)
	AbsolURL(bu, u)
	StringToList(k)
	offsetOf(theItem, theList)

Here is the output when run on Jon Pugh’s smartStrings library


SmartString
		newString(aString) --> script object factory method
		getString() --> string (only reads)
		setString(aString) --> string
		setList(aList, seperator) --> string
		subString(x, y) --> string (only reads)
		beforeString(aString) --> string (only reads)
		afterString(aString) --> string (only reads)
		betweenStrings(afterThis, beforeThis) --> string (only reads)
		appendString(aString) --> string
		prependString(aString) --> string
		replaceString(thisStr, thatStr) -- syntax forgivenness so you don't have to remember if there is or isn't an s
		replaceStrings(thisStr, thatStr) --> string
		deleteString(aString) --> string
		replaceBetween(frontTag, rearTag, newValue) --> string
		insertBefore(beforeStr, thisStr) --> string
		insertAfter(afterStr, thisStr) --> string
		deleteBefore(beforeStr) --> string
		deleteAfter(afterStr) --> string
		deleteBetween(afterThis, beforeThis) --> string
		keepBefore(beforeStr) --> string
		keepAfter(afterStr) --> string
		keepBetween(afterThis, beforeThis) --> string
		deleteCharacters(x, y) --> string
		convertListToString(aList, delim) --> string (only reads)
		getTokens(delim) --> list (only reads)
		setTokens(aList, delim) --> string
		firstToken(delim) --> string (only reads)
		lastToken(delim) --> string
		NthToken(n, delim) --> string
		deleteNthToken(n, delim) --> string
		deleteFirstToken(delim) --> string
		deleteLastToken(delim) --> string
		keepFirstToken(delim) --> string
		keepLastToken(delim) --> string
		keepNthToken(n, delim) --> string
		getTokenRange(startIndex, endIndex, delim) --> list (only reads)
		deleteTokenRange(startIndex, endIndex, delim) --> string
		keepTokenRange(startIndex, endIndex, delim) --> string
		beforeToken(token, delim) --> string (only reads)
		afterToken(token, delim) --> string (only reads)
		deleteTokensAfter(token, delim) --> string
		keepTokensAfter(token, delim) --> string
		trimWhitespace() --> string
		uppercase {} --> string
		lowercase {} --> string
		abs (n)

<drumroll(>
And here follows the code.


-- A library lister that returns a hierarchic view of script objects and handlers in an as Library file.
-- a nice feature would be to exclude an opened file from the recent items menu.
-- The Idea and implementation and any faults is totally mine. © McUsr 2010 and put in the Public Domain.
-- The usually guarrantees about nothing what so ever applies, use it at your own risk.
-- Read the documentation.
-- You are not allowed to post this code elsewhere, but may of course refer to the post at macscripter.net.
” macscripter.net/viewtopic.php?pid=131154#p131154
(*
TERMS OF USE. 
This applies only to posting code, as long as you don't post it, you are welcome to do
whatever you want to do with it without any further permission. 
Except for the following: Selling the code as is, or removing copyright statmentents and the embedded link in the code (without the http:// part) from the code. 

You must also state what you eventually have done with the original source. This obviously doesn't matter if you distribure AppleScript as read only. I do not require you to embed any properties helding copyright notice for the code.

Credit for having contributed to your product would in all cases be nice!

If you use this code as part of script of yours you are of course welcome to post that code with my code in it here at macscripter.net. If you then wish to post your code elsewhere after having uploaded it to MacScripter.net please email me and ask for permission.

The ideal situation is however that you then refer to your code by a link to MacScripter.net
The sole reason for this, is that it is so much better for all of us to have a centralized codebase which are updated, than having to roam the net to find any usable snippets. Which some of us probabaly originated in the first hand.

I'm picky about this. If I find you to have published parts of my code on any other site without previous permission, I'll do what I can to have the site remove the code, ban you, and sue you under the jurisdiction of AGDER LAGMANNSRETT of Norway. Those are the terms you silently agree too by using this code. 

The above paragraphs are also valid if you violate any of my terms.

If you use this or have advantage of this code in a professional setting, where professional setting means that you use this code to earn money by keeping yourself more productive. Or if you as an employee share the resulting script with other coworkers, enhancing the productivity of your company, then a modest donation to MacScripter.net would be appreciated.
*)
property MAXLEVEL : 4
on run
	script o
		property pxLibPath : ""
	end script
	
	local txtOfLibraryScript
	local commentlist, level1ScriptObjects, level1Handlers, level2Handlers, scriptObjectEntities, washingCandidates, MergedList, childList
	local callCount, startPattern, tabString
	
	set {commentlist, level1ScriptObjects, level1Handlers, level2Handlers, scriptObjectEntities, washingCandidates, MergedList} to {{}, {}, {}, {}, {}, {}, {}}
	set {callCount, tabString} to {0, ""}
	
	set txtOfLibraryScript to getLibraryText(a reference to o's pxLibPath)
	
	getBlockCommentPositions(txtOfLibraryScript, commentlist)
	-- Collects level 1 script objects.
	set level1ScriptObjects to makeEntityList(("^" & tabString & "(script|using terms from)[ ](.*$)"), "\\2", txtOfLibraryScript)
	
	set level1ScriptObjects to washOutBlockCommentedEntities(level1ScriptObjects, commentlist)
	
	-- collects level 1 handlers.
	set level1Handlers to makeEntityList(("^" & tabString & "(on|to)[ ](.*$)"), "\\2", txtOfLibraryScript)
	
	set level1Handlers to washOutBlockCommentedEntities(level1Handlers, commentlist)
	
	set level1Handlers to washOutOnErrorStatements(level1Handlers)
	
	if level1ScriptObjects is {} and level1Handlers is {} then
		tell me
			activate
			display alert "There were no script objects nor handlers in  in " & o's pxLibPath & "!"
			error number -128
		end tell
	end if
	
	set {tabString, level2Handlers} to {"	", {}} -- uses the new value for retrieving any level 2 script objects as well.
	if level1ScriptObjects is not {} then
		-- collect any level 2 handlers.
		set level2Handlers to makeEntityList(("^" & tabString & "(on|to)[ ](.*$)"), "\\2", txtOfLibraryScript)
		set level2Handlers to washOutBlockCommentedEntities(level2Handlers, commentlist)
		set level2Handlers to washOutOnErrorStatements(level2Handlers)
		set level2Handlers to prependIndent(tabString, level2Handlers)
		-- Merge the Level2 handlers with the ScriptObjects List and create a Candidates list for washing at the same time.
		set scriptObjectEntities to mergeHandlersFindPossibleEmptyScriptObjects(level2Handlers, level1ScriptObjects, scriptObjectEntities, washingCandidates, callCount, MAXLEVEL)
		set level2Handlers to missing value
	end if
	
	if level1Handlers is not {} and scriptObjectEntities is not {} then
		-- Merges level1Handlers with scriptObjectEntites before adding any children.
		local HandlerFirstPos, HandlerItemNo, HandlerCount, ScriptObjectFirstPos, ScriptObjectItemNo, ScriptObjectCount
		set {HandlerFirstPos, HandlerItemNo, HandlerCount} to {matchPos of item 1 of level1Handlers, 1, (count level1Handlers)}
		set {ScriptObjectFirstPos, ScriptObjectItemNo, ScriptObjectCount} to {matchPos of item 1 of scriptObjectEntities, 1, (count scriptObjectEntities)}
		
		repeat while ((ScriptObjectItemNo ≤ ScriptObjectCount) or (HandlerItemNo ≤ HandlerCount))
			if (ScriptObjectItemNo > ScriptObjectCount) then
				set end of MergedList to item HandlerItemNo of level1Handlers
				set HandlerItemNo to HandlerItemNo + 1
			else if (HandlerItemNo > HandlerCount) then
				set end of MergedList to item ScriptObjectItemNo of scriptObjectEntities
				set ScriptObjectItemNo to ScriptObjectItemNo + 1
			else if (matchPos of contents of item HandlerItemNo of level1Handlers < matchPos of contents of item ScriptObjectItemNo of scriptObjectEntities) then
				set end of MergedList to item HandlerItemNo of level1Handlers
				set HandlerItemNo to HandlerItemNo + 1
			else
				set end of MergedList to item ScriptObjectItemNo of scriptObjectEntities
				set ScriptObjectItemNo to ScriptObjectItemNo + 1
			end if
		end repeat
		
	else if level1Handlers is {} then
		set MergedList to scriptObjectEntities
	else -- had to have  handlers, -- we had to to get here.
		set MergedList to level1Handlers
	end if
	-- we are now getting at level 2 script objects, i.e at the same level as the level2 handlers.
	set childList to getScriptObjectEntities(tabString, txtOfLibraryScript, commentlist, callCount, MAXLEVEL)
	
	-- following handlers check for an empty child list
	if washingCandidates ≠ {} then
		set MergedList to washParentListForEmptyObjects(MergedList, childList, level1ScriptObjects, washingCandidates)
	end if
	set {level1ScriptObjects, washingCandidates} to {missing value, missing value}
	set MergedList to mergeCleanLists(MergedList, childList)
	set childList to missing value
	
	-- extracts the result for viewing
	set displayList to {}
	repeat with anItem in MergedList
		set end of displayList to matchResult of anItem
	end repeat
	tell me
		activate
		choose from list displayList default items (item 1 of displayList) with prompt o's pxLibPath
	end tell
	set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, return}
	set displayList to text items of displayList as text
	set AppleScript's text item delimiters to tids
	set test to "A ;-)"
end run

on getLibraryText(aRefToPxFile) ” Thanks to oldmanegan
	-- returns text of Script library upon success
	local theSourceText, thisScript, qfPoxPath
	set thisScript to (choose file) as text
	set contents of aRefToPxFile to POSIX path of thisScript
	set qfPoxPath to quoted form of POSIX path of thisScript as text
	
	try
		set theSourceText to do shell script "/usr/bin/osadecompile " & qfPoxPath & "| /usr/bin/tr -d '\\000'"
	on error e number n
		display alert e & " : " & n
		error number -128
	end try
	if theSourceText is "" then
		display alert "The Script is empty, or left in debugging state"
		error number -128
	end if
	return theSourceText
end getLibraryText


on getScriptObjectEntities(tabIndent, theTextToPeruse, commentlist, callCount, MAXLEVEL)
	-- Collects a script object with its contents. returns a single indented list of items.
	-- Emtpy script objects washed away.
	-- CONTEXT: The first level of both handlers and script objects and the second level of handlers is already parsed
	local tabCount, originalIndent, ScriptObjectList, HandlerList
	set {tabCount, originalIndent, ScriptObjectList, callCount} to {1, tabIndent, {}, callCount + 1}
	-- Collects script objects.
	set ScriptObjectList to makeEntityList(("^" & tabIndent & "(script|using terms from)[ ](.*$)"), "\\2", theTextToPeruse)
	set ScriptObjectList to washOutBlockCommentedEntities(ScriptObjectList, commentlist)
	set ScriptObjectList to prependIndent(tabIndent, ScriptObjectList)
	
	if ScriptObjectList is not {} then -- END CONDITON FOR RECURSION
		-- Finds any handlers in the script objects so far.
		set {tabIndent, HandlerList} to {(tabIndent & "	"), {}}
		set HandlerList to makeEntityList(("^" & tabIndent & "(on|to)[ ](.*$)"), "\\2", theTextToPeruse)
		set HandlerList to washOutBlockCommentedEntities(HandlerList, commentlist)
		set HandlerList to washOutOnErrorStatements(HandlerList)
		set HandlerList to prependIndent(tabIndent, HandlerList)
		if HandlerList is {} and callCount is MAXLEVEL then return {}
	else
		return {}
	end if
	
	-- Merges the eventual outcome of previous gathering into single lists, marks possible empty script objects for washing.
	local MergedList, washingCandidates
	set {MergedList, washingCandidates} to {{}, {}}
	
	set MergedList to mergeHandlersFindPossibleEmptyScriptObjects(HandlerList, ScriptObjectList, MergedList, washingCandidates, callCount, MAXLEVEL)
	if HandlerList is {} then
		set washingCandidates to indexByNextAList(ScriptObjectList)
	end if
	
	set HandlerList to missing value
	
	if callCount is MAXLEVEL then
		-- washes out any candidates from findings in mergeHandlersFindPossibleEmptyScriptObjects
		set MergedList to washMergeListForEmptyScriptObjects(MergedList, washingCandidates)
		return MergedList
	else
		-- Washes away empty script objects with our aquired facts.
		set tabIndent to originalIndent & "	"
		set childList to getScriptObjectEntities(tabIndent, theTextToPeruse, commentlist, callCount, MAXLEVEL)
		if childList is not {} then
			-- following handlers check for an empty child list
			set MergedList to washParentListForEmptyObjects(MergedList, childList, ScriptObjectList, washingCandidates)
		else
			-- just removes any items of the washlist one bye one.
			set MergedList to washMergeListForEmptyScriptObjects(MergedList, washingCandidates)
		end if
		set {ScriptObjectList, washingCandidates} to {missing value, missing value}
		set MergedList to mergeCleanLists(MergedList, childList)
		set childList to missing value
		return MergedList
	end if
end getScriptObjectEntities


on mergeHandlersFindPossibleEmptyScriptObjects(HandlerList, ScriptObjectList, MergedList, washingCandidates, callCount, MAXLEVEL)
	-- returns a list with merged handlers also tags any script objects empty so far.
	local HandlerFirstPos, HandlerItemNo, HandlerCount, ScriptObjectFirstPos, ScriptObjectItemNo, ScriptObjectCount, lastWasScriptObject
	if HandlerList is not {} then
		
		set {HandlerFirstPos, HandlerItemNo, HandlerCount} to {matchPos of item 1 of HandlerList, 1, (count HandlerList)}
		set {ScriptObjectFirstPos, ScriptObjectItemNo, ScriptObjectCount} to {matchPos of item 1 of ScriptObjectList, 2, (count ScriptObjectList)}
		set {MergedList, end of MergedList, lastWasScriptObject} to {{}, item 1 of ScriptObjectList, true}
		
		repeat while ((ScriptObjectItemNo ≤ ScriptObjectCount) or (HandlerItemNo ≤ HandlerCount))
			if (ScriptObjectItemNo > ScriptObjectCount) then
				
				
				set lastWasScriptObject to false -- because: there isn't any other script object to add at this moment.
				set end of MergedList to item HandlerItemNo of HandlerList
				set HandlerItemNo to HandlerItemNo + 1
				
			else if HandlerItemNo > HandlerCount then
				
				if lastWasScriptObject is true then
					set end of washingCandidates to {ScriptObjectItemNo, item -1 of MergedList}
					set lastWasScriptObject to false
				else
					set end of MergedList to item ScriptObjectItemNo of ScriptObjectList
					
					set end of washingCandidates to {(ScriptObjectItemNo + 1), item ScriptObjectItemNo of ScriptObjectList}
					
					set ScriptObjectItemNo to ScriptObjectItemNo + 1
					
				end if
				
			else if (matchPos of contents of item HandlerItemNo of HandlerList < matchPos of contents of item ScriptObjectItemNo of ScriptObjectList) then
				set lastWasScriptObject to false
				set end of MergedList to item HandlerItemNo of HandlerList
				set HandlerItemNo to HandlerItemNo + 1
				
			else -- Script object has a lower position value
				
				if lastWasScriptObject is false then
					set end of MergedList to item ScriptObjectItemNo of ScriptObjectList
					
				else -- two script objects in a row
					
					set end of washingCandidates to {ScriptObjectItemNo, item -1 of MergedList}
					set end of MergedList to item ScriptObjectItemNo of ScriptObjectList
				end if
				set ScriptObjectItemNo to ScriptObjectItemNo + 1
				set lastWasScriptObject to true
			end if
		end repeat
		if lastWasScriptObject is true then set end of washingCandidates to {ScriptObjectItemNo, item -1 of MergedList}
		
		set HandlerList to missing value
	else
		set MergedList to ScriptObjectList
	end if
	return MergedList
end mergeHandlersFindPossibleEmptyScriptObjects


on mergeCleanLists(parentList, childList)
	-- Merges two lists, by order of matchpos, returns result
	-- the ParentList is higher up in the hierarchy, therefore contains preceding elements.
	if not childList is {} then -- superfluos but
		local resultList, parentItemNo, parentCount, childItemNo, childCount
		set {parentItemNo, parentCount, childItemNo, childCount} to {2, (count parentList), 1, (count childList)}
		set {resultList, end of resultList} to {{}, item 1 of parentList}
		-- Safely assume that first item in merg list has lower pos than first in child list
		
		repeat while ((parentItemNo ≤ parentCount) or (childItemNo ≤ childCount))
			if (parentItemNo > parentCount) then
				
				set end of resultList to item childItemNo of childList
				set childItemNo to childItemNo + 1
				
			else if (childItemNo > childCount) then
				
				set end of resultList to item parentItemNo of parentList
				set parentItemNo to parentItemNo + 1
			else if (matchPos of contents of item childItemNo of childList < matchPos of contents of item parentItemNo of parentList) then
				
				set end of resultList to item childItemNo of childList
				set childItemNo to childItemNo + 1
				
			else
				
				set end of resultList to item parentItemNo of parentList
				set parentItemNo to parentItemNo + 1
				
			end if
		end repeat
		return resultList
	else
		return parentList
	end if
end mergeCleanLists


on washParentListForEmptyObjects(parentList, childList, origScrObjList, washList)
	-- compares elements in parent list with child list and washlist.
	-- draws a conclusion wether parent element is really empty, then removes it. 
	local parentItemNo, parentCount, childItemNo, washItemNo, washCount, washFirstPos, washLastPos, foundLaterChild, curChildPos
	set {parentItemNo, parentCount, childItemNo, childCount, washItemNo, washCount} to {1, (count parentList), 1, (count childList), 1, (count washList)}
	
	if washList ≠ {} and childList ≠ {} then
		
		set washFirstPos to matchPos of item 2 of item washItemNo of washList
		set washLastPos to matchPos of item (item 1 of item washItemNo of washList) of origScrObjList
		
		repeat while washItemNo ≤ washCount
			
			set foundLaterChild to false
			
			repeat with i from childItemNo to childCount
				
				set curChildPos to matchPos of item i of childList
				if curChildPos > washFirstPos and curChildPos < washLastPos then
					set washItemNo to washItemNo + 1
					set childItemNo to childItemNo + 1
					set foundLaterChild to true
					exit repeat -- found sibling keeps this candidate
					
				else if curChildPos > washLastPos then -- must be less than, positions are unique
					set foundLaterChild to true -- we bypassed the candidate
					
					repeat with j from parentItemNo to parentCount
						if matchPos of item j of parentList = washFirstPos then
							set item j of parentList to missing value
							set mergeItemNo to j + 1
							exit repeat
						end if
					end repeat
					set washItemNo to washItemNo + 1
					set foundLaterChild to true
					exit repeat
				end if
			end repeat
			
			if foundLaterChild is false then
				-- all there is to delete every member of washingList from parentList
				repeat with i from washItemNo to washCount
					repeat with j from parentItemNo to contents of parentCount
						if matchPos of item j of parentList = washFirstPos then
							set item j of parentList to missing value
							set parentItemNo to j + 1
							exit repeat
						end if
						-- set washFirstPos to matchPos of item 2 of item (i + 1) of washList
						if i < washCount then
							set washFirstPos to matchPos of item 2 of item (i + 1) of washList
						else
							exit repeat
						end if
					end repeat
				end repeat
				exit repeat -- done WASHING
			else
				
				set washFirstPos to matchPos of item 2 of item washItemNo of washList
				set washLastPos to matchPos of item (item 1 of item washItemNo of washList) of origScrObjList
				
			end if
			
		end repeat
		set parentList to parentList's records
		-- else -just return the parentList
	end if
	return parentList
end washParentListForEmptyObjects

on washMergeListForEmptyScriptObjects(mergeList, washList)
	-- cleans a list for empty script objects,when there were no new children.
	local mergeItemNo, washItemNo, washCount, washFirstPos
	set {mergeItemNo, mergeCount, washItemNo, washCount} to {1, (count mergeList), 1, (count washList)}
	
	if washList ≠ {} then
		
		repeat with i from washItemNo to washCount
			
			set washFirstPos to matchPos of item 2 of item i of washList
			
			repeat with j from mergeItemNo to mergeCount
				if matchPos of item j of mergeList = washFirstPos then
					set item j of mergeList to missing value
					set mergeItemNo to j + 1
					exit repeat
				end if
			end repeat
		end repeat
		-- else -- nothing to merge nor wash 
		set mergeList to mergeList's records
	else
		return mergeList
	end if
end washMergeListForEmptyScriptObjects


on washOutBlockCommentedEntities(entityList, blockCommentList)
	-- removes any entity that is within a block comment.
	script o
		property l : entityList
	end script
	if not entityList is {} then
		repeat with i from 1 to (count entityList)
			if withinABlockComment(matchPos of (contents of item i of o's l), blockCommentList) then
				set item i of o's l to missing value
			end if
		end repeat
		set entityList to o's l's records
	end if
	return entityList
end washOutBlockCommentedEntities

on washOutOnErrorStatements(HandlerList)
	-- removes the "on error" statements.
	script o
		property l : HandlerList
	end script
	if not HandlerList is {} then
		repeat with i from 1 to (count HandlerList)
			if matchResult of (contents of item i of o's l) is "error" or matchResult of (contents of item i of o's l) starts with "error " then
				set item i of o's l to missing value
			end if
		end repeat
		set HandlerList to o's l's records
	end if
	return HandlerList
end washOutOnErrorStatements

on makeEntityList(seachString, searchItemNr, theTextToPeruse)
	-- Harvests out a list of entities of type specified by parameters.
	script o
		property l : theTextToPeruse
	end script
	local theRes
	set theRes to find text seachString in o's l using searchItemNr starting at 0 with regexp and all occurrences
	return theRes
end makeEntityList

on prependIndent(tabIndent, HandlerList)
	-- idents the element after we have washed out error statments for handlers
	-- anytime for script objects
	script o
		property l : HandlerList
	end script
	repeat with i from 1 to (count HandlerList)
		set matchResult of (item i of o's l) to tabIndent & (matchResult of (contents of item i of o's l))
	end repeat
	return o's l
end prependIndent

-- tells us if a pos of a particular handler is witihin a block comment.
on withinABlockComment(aPos, listOfBlockPos)
	script o
		property l : listOfBlockPos
	end script
	repeat with i from 1 to (count listOfBlockPos)
		if item 1 of item i of o's l < aPos and item 2 of item i of o's l > aPos then return true
	end repeat
	return false
end withinABlockComment

on getBlockCommentPositions(theTextToPeruse, listOfBlockPos)
	-- creates a list with start and end positons of  block comments
	script o
		property l : theTextToPeruse
		property m : listOfBlockPos
	end script
	local startPattern, SearchPattern, frompos, theRes, PrevPos
	set startPattern to true
	set SearchPattern to "^([(][*])"
	set frompos to 0
	repeat
		try
			set theRes to find text SearchPattern in o's l using "\\0" starting at frompos with regexp
			set startPattern to not startPattern
			if startPattern then
				-- it was the end pattern we just found
				set SearchPattern to "^([(][*])"
				-- saving our results
				try
					copy {PrevPos, matchPos of theRes} to end of o's m
				on error e number n
					display alert e & " : " & n
				end try
			else
				copy matchPos of theRes to PrevPos
				-- it was the start pattern we just found
				set SearchPattern to "^([*][)])"
			end if
			set frompos to ((matchPos of theRes) + (matchLen of theRes) + 1)
		on error
			-- we better having erred on the first RegExp.
			if not startPattern then
				error "malformed comments - can't happen"
			end if
			exit repeat
		end try
	end repeat
	return listOfBlockPos
end getBlockCommentPositions

on indexByNextAList(listToIndex)
	-- returns a list of lists, with item in list preceeded by its index+1.
	script o
		property l : listToIndex
	end script
	local newList
	set newList to {}
	repeat with i from 1 to (count listToIndex)
		set end of newList to {(i + 1), item i of o's l}
	end repeat
	return newList
end indexByNextAList


Hello.
Removed an undeleted line, which shouldn’t be there in order to create correct indenting.

It now indents correctly

I have updated the doc, to a state that I will leave it int. Until I actually uses it in some script, which may be sooner than you think! :slight_smile: