Convert the Text to Record (effectively)

Thanks KniazdisR–that got it working. I’ll modify my post above.

I cleaned up a version from above to save fields that are integers as integers not text.
It also does REALS and BOOLEANS.
Also i only call run script once to cut down on processing time


set theContent to "agrCtlRSSI: -29
agrExtRSSI: true
agrCtlNoise: -87
agrExtNoise: 2.10
state: running
op mode: station 
lastTxRate: 59
maxRate: 72
lastAssocStatus: 0
802.11 auth: open
link auth: wpa2-psk
BSSID: 8:78:8:0:aa:5c
SSID: AndroidAP
MCS: 6
channel: 6"

set text item delimiters to {": "}
set theContent to (theContent's text items)
set text item delimiters to {":"}
set theContent to theContent as text
set theContent to paragraphs of the theContent
set {keylist, textList, recordz} to {{}, {}, {}}

repeat with index from 1 to count theContent
	set anItem to theContent's item index
	set keylist's end to anItem's text item 1
	if (count (text items of anItem)) = 2 then
		set textList's end to anItem's text item 2
	else
		set textList's end to (anItem's text items 2 thru -1) as text
	end if
	try -- see if field is an boolean
		set textList's last item to textList's last item as boolean
		set end of recordz to ("|" & my keylist's item index & "|: " & (textList's item index))
	on error
		try -- see if field is a REAL
			if textList's last item contains "." then
				set textList's last item to textList's last item as real
			else
				error
			end if
			set end of recordz to ("|" & my keylist's item index & "|: " & (textList's item index))
		on error -- its text
			try -- see if field is an integer
				set textList's last item to textList's last item as integer
				set end of recordz to ("|" & my keylist's item index & "|: " & (textList's item index))
			on error -- its text
				set end of recordz to ("|" & my keylist's item index & "|: \"" & my textList's item index & "\"")
			end try
		end try
	end try
end repeat
set text item delimiters to {","}
set recordz to run script "{" & (recordz as text) & "}"
recordz

Very cool! If you want to handle reals and integers:


            set textList's last item to textList's last item as number

Should be easy enough to do booleans also.

Done!

I edited my post above.

Yes, they are. I think the length of time for which you’ve been saying this sort of demonstrates their permanency, and they’ve most certainly outlived many apparently stable “core” features of AppleScript in their lifetime. So I think when it comes to defining what the term “legitimate” actually means, I’m taking the pragmatic view in this instance (and I must admit, pragmatism doesn’t always come easily to me), which is to acknowledge that these things have utility, they’re not going anywhere, and they let one do things that aren’t possible otherwise. I’ve previously given a run-down on their history in AppleScript, what capabilities they retain, and the pros and cons in various scenarios.

Yes, correct. They aren’t serving a functional purpose in this case, purely a typographical one, largely for my benefit to be able to distinguish neighbouring brackets from each other more easily. Anyone else who’s dyslexic might understand how confusing consecutive sets of symbols can be.

I actually normally switch them back out for curly ones when publishing, but my tiredness made me overlook it in this instance. That said, they pose no drawbacks here nor do any harm, and if anything, it’s opened up discussion for people to learn about it.

Oh, and those tab characters shouldn’t be there. I don’t know whether that’s something you inserted into the script or whether that was me again. Simply appending a tab item to the tids value in question will resolve their unwelcome appearance.

There’s no particular reason on the face of it, or on the back of it, or from any angle, to discourage their use. Once again, it’s about appreciating that they have utility, but also—as you rightly said—knowing and understanding how to use them in ways that maintain robustness and offer effectiveness. Come to think of it, that’s true of everything.

I would encourage you to try and start making a point to use them, even though it may be outside of your comfort zone. But getting familiar with something usually allows reservations that are less warranted to subside. You may even end up liking it.

Ah, is that where they’re coming from ? I don’t know whether the forum mutated the white space or whether I did it in a hypnogogic state.

Yes, really sorry. I was half asleep last night and made a few blunders. I’ve amended my post to correct these.

try blocks will slow a script down quite significantly when an error is caught. Error catching is a fairly intense set of operations that get performed in order to catch the error, itemise the components of the script responsible for the error, reporting it, and logging it. Luckily, you can probably do away with all of that and simply do this:

tell the textList to set the last item to the last item as {boolean, real, integer, text}

Thanks, thanks, thanks!

I ended up with 3 solutions that return the exact result, but at different speeds. The most effective method (for me) here was the JXA-method, provided by @CK. I also discovered a lot of interesting things about his programming technique.

  1. JXA solution from @CK turned out to be the fastest (3 ms). It is ideal for users who understand JXA. See my post #10 for how I applied this solution.

  2. The method suggested by @Marc Anthony is also fast (9 ms), although it is 3 times slower than the JXA solution. Since the difference in speed is not fatal, this solution is ideal for users weak in JXA. See my post #13 for how I applied this 2nd solution.

  3. The method suggested by @robertfern is the most accurate solution, so it’s worth considering. It is ideal for those who would like to receive the values of numerical keys in numerical form. I tried to get the most speed out of it, and made it to remove spaces before key names. I ended up with a script that is 10 times slower than the JXA solution (30 ms):


on currentWiFiNetworkInfo()
	script o
		property currentWiFiNetworkInfo : ""
	end script
	set o's currentWiFiNetworkInfo to paragraphs of (do shell script "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I")
	set {ATID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ": "}
	set myRec to {}
	repeat with i in o's currentWiFiNetworkInfo
		set tmp to text items of i
		set spaceOffset to item 1 of tmp
		repeat while spaceOffset begins with space -- added by me
			set spaceOffset to text 2 thru -1 of spaceOffset
		end repeat
		set end of myRec to spaceOffset
		set end of myRec to (rest of tmp) as {integer, text} -- the @CK's last suggestion applied here
	end repeat
	set AppleScript's text item delimiters to ATID
	set rawRecord to {«class usrf»:myRec}
end currentWiFiNetworkInfo

my currentWiFiNetworkInfo()

Hmmm… I tried various forms of that, and it changes the class of everything to text. But the below works.


set theContent to "agrCtlRSSI: -29
agrExtRSSI: true
 agrCtlNoise: -87
agrExtNoise: 2.10
state: running
		op mode: station 
 lastTxRate: 59
maxRate: 72
lastAssocStatus: 0
  802.11 auth: open
link auth: wpa2-psk
  BSSID: 8:78:8:0:aa:5c
 SSID: AndroidAP
MCS: 6
channel: 6"
set testRecords to RecordIzeText(theContent)
set currentWiFiNetworkInfo to do shell script "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I"
set actualRecords to RecordIzeText(currentWiFiNetworkInfo)

on RecordIzeText(theContent)
	set the content to linefeed & theContent
	set saveTID to AppleScript's text item delimiters
	repeat
		set AppleScript's text item delimiters to {return & tab, linefeed & tab, return & space, linefeed & space}
		set fixedText to text items of theContent
		set AppleScript's text item delimiters to {linefeed}
		set fixedText to fixedText as text
		if fixedText = theContent then exit repeat
		set theContent to fixedText
	end repeat
	set the theContent to the rest of paragraphs of (theContent as text)
	set recordz to {}
	set text item delimiters to {": "}
	repeat with index from 1 to count theContent
		set anItem to theContent's item index
		set anItem to text items of anItem
		set recordLabel to item 1 of anItem
		set recordValue to the rest of anItem as text
		--set recordValue to recordValue as {boolean, real, integer, text} --turns everything to text
		try
			set recordValue to recordValue as {boolean, number}
		on error
			set recordValue to quote & recordValue & quote
		end try
		set end of recordz to ("|" & recordLabel & "|: " & (recordValue))
	end repeat
	set text item delimiters to {","}
	set recordz to run script "{" & (recordz as text) & "}"
	set AppleScript's text item delimiters to saveTID
	return recordz
end RecordIzeText

Also…

Script Debugger adds a lot of overhead to script execution in order to do all its magic. That makes it less than ideal for timing scripts. I think that’s why Shane made Script Geek in the first place.

They’ve not been mentioned in any edition of the AppleScript Language Guide for at least 25 years. So either the author(s) have been very forgetful or the AppleScript team hasn’t intended square brackets to be an official part of the language. The latter explanation’s by far the more likely. Square brackets were mentioned once or twice in my very early days on the AppleScript-Users e-mail list, but you and one other poster elsewhere (possibly the same person posting under a different name) are the only people I’ve ever seen actually use them. The length of time I’ve had to point out that they’re not (or are no longer) an official part of AppleScript equates to the time you’ve been casually using them here without explanation or good reason. It’s the permanency of your stubbornness rather than that of square brackets that’s being demonstrated.

AppleScript has quite a few undocumented features and sometimes they can be the only way to get a particular job done or to get it done efficiently (if you know about them). Where this is the case, it should be noted and explained in comments. Where not, more standard methods should be used. This isn’t some law I’ve made up myself. It’s a philosophy passed on by the many professional coders with whom I’ve crossed swords over the past quarter century. Likewise with not using reserved terms (with a few exceptions) as labels for script and user record properties. But as I noted above, your convoluted script seems to depend on the labels not being user ones.

Not being casual or a smartarse is especially relevant in fora where people come for help in learning the language. There are subtle differences between lists with square brackets (“linked lists”) and those with braces (“vectors”). If a learner sees someone who comes on clever casually using square brackets instead of braces simply as a matter of style, they might be tempted to think the two are equivalent, follow suit, and one day run foul of the differences. That said, of course, there’s no reason why an undocumented feature shouldn’t be demo’d at the end of a relevant, already answered topic for the interest and amusement of other scripters.

I in turn would encourage you to read MacScripter’s forum rules, which were also not made up by me. Rule 2 states: “You will be required to enter your legal first and last name when you register.” The first and last name shown in your profile don’t conform to this requirement and I’d encourage you to correct them before Mark notices. :slight_smile:

If anyone’s interested, in April 2002, on the AppleScript-Users list, Shane quoted from a 1993 AppleScript document which appears to show the beginning of linked lists’ fall from favour. By 1997, there was no mention of them at all in the AppleScript documentation.

Here’s an analysis of CK’s AppleScript script in post #5. The way the TIDs are used is interesting.

set WifiStats to "		agrCtlRSSI: -29
		agrExtRSSI: 0
		agrCtlNoise: -87
		agrExtNoise: 0
		state: running
		op mode: station 
		lastTxRate: 59
		maxRate: 72
		lastAssocStatus: 0
		802.11 auth: open
		link auth: wpa2-psk
		BSSID: 8:78:8:0:aa:5c
		SSID: AndroidAP
		MCS: 6
 channel: 6"

-- Replace both linefeeds and colon-spaces with linefeeds.
set AppleScript's text item delimiters to {linefeed, ": "}
set WifiStats to WifiStats's text items as text
-- Replace both spaces and tabs with empty texts resulting from a non-text delimiter.
set AppleScript's text item delimiters to {{space}, tab} -- simile: {7, space, tab}
set WifiStats to WifiStats's text items as text
-- Hack the result's alternating label and value lines into a record.
return {«class usrf»:WifiStats's paragraphs}'s contents as anything

-- Another once-popular version of the hack:
return (record {«class usrf»:WifiStats's paragraphs} as record)'s «class seld»

-- Or even:
script
	{«class usrf»:WifiStats's paragraphs}
end script
return (run script result)

The purpose of KniazidisR’s script appears to be to get information from the airport utility, and, if this is correct, an ASObjC solution is probably of little worth. However, FWIW, the following ASObjC script does the job and takes 4 milliseconds (with the Foundation framework in memory), which is competitive with the other suggestions. I had earlier posted a different version of this script, but the following is faster because it uses ": " as a separator to parse each paragraph of the source string. Error correction is needed if blank or nonconforming paragraphs are possible.

use framework "Foundation"

set theString to "agrCtlRSSI: -29
agrExtRSSI: 0
agrCtlNoise: -87
agrExtNoise: 0
state: running
op mode: station 
lastTxRate: 59
maxRate: 72
lastAssocStatus: 0
802.11 auth: open
link auth: wpa2-psk
BSSID: 8:78:8:0:aa:5c
SSID: AndroidAP
MCS: 6
channel: 6"

on getRecord(theString)
	set theString to current application's NSString's stringWithString:theString
	set theArray to (theString's componentsSeparatedByString:linefeed)
	set theDictionary to (current application's NSMutableDictionary's new())
	set whiteSpaceCharacters to current application's NSCharacterSet's whitespaceCharacterSet()
	repeat with aParagraph in theArray
		set paragraphArray to (aParagraph's componentsSeparatedByString:": ")
		set theKey to ((paragraphArray's objectAtIndex:0)'s stringByTrimmingCharactersInSet:whiteSpaceCharacters)
		set theValue to ((paragraphArray's objectAtIndex:1)'s stringByTrimmingCharactersInSet:whiteSpaceCharacters)
		(theDictionary's setObject:theValue forKey:theKey)
	end repeat
	return theDictionary as record
end getRecord

set theRecord to getRecord(theString)

@peavine,

I tested your latest script. It runs for me in 9 ms, that is, it is 3 times slower than a JXA script. I think it’s because of the use of a repeat loop, and the AsObjC solution can only get faster than the JXA solution with the correct Regex expression applying, without repeat loops.

It’s unfortunate that the results from the airport command are so sloppy and haphazard. Multiple lines randomly begin with spaces, and a single line ends with multiple; the need for correction lends itself to less than optimal efficiency—no matter the method. Since my initial attempt eliminated internal spaces, I played around with a regex and married it to CK’s initial approach. It may or may not be faster, but it’s brief. :slight_smile:


({«class usrf»:(do shell script "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I | egrep -o '\\w[^:]+|:.+' | awk '{gsub(\" +$|: \",\"\"); print} ' ")'s paragraphs})'s contents as anything

It’s not just the airport utility and its sloppy and haphazard result, but the fact that many other utilities produce a similar key-value structure. Therefore, I wanted to make sure which approach is the most effective in such cases. Some utilities has filtering options for its output, but still doesn’t return it as record. In this topic, I made sure that using a regular expression is the most effective solution.

I tested your last script and I can confirm that it works instantly (2 ms), i.e. 1 ms faster than the JXA solution.

Thanks, this will come in handy.

Here’s an ASObjC solution using regex. It renders number values in the final record as numbers rather than as text.

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions

set WifiStats to "		agrCtlRSSI: -29 
		agrExtRSSI: 0
		agrCtlNoise: -87
		agrExtNoise: 0
		state: running
		op mode: station 
		lastTxRate: 59
		maxRate: 72  
		lastAssocStatus: 0
		802.11 auth: open
		link auth: wpa2-psk
		BSSID: 8:78:8:0:aa:5c
		SSID: AndroidAP
		MCS: 6
 channel: 6"

set regex to current application's NSRegularExpressionSearch
tell (current application's class "NSMutableString"'s stringWithString:(WifiStats))
	-- Trim leading and trailing white space and empty lines.
	its replaceOccurrencesOfString:("(?m)^\\s++|\\h++$|\\s++$") withString:("") options:(regex) range:({0, its |length|()})
	-- Enbar labels and enquote values.
	its replaceOccurrencesOfString:("(?m)^([^:]++):\\h++(.++)") withString:("|$1|: \"$2\"") options:(regex) range:({0, its |length|()})
	-- Unenquote number values.
	its replaceOccurrencesOfString:("\"([0-9.-]++)\"") withString:("$1") options:(regex) range:({0, its |length|()})
	-- Replace internal line endings with commas.
	its replaceOccurrencesOfString:("\\R++") withString:(", ") options:(regex) range:({0, its |length|()})
	-- Concatenate between AS text braces (thereby also coercing to AS text) and run as script code.
	return my (run script ("{" & it & "}"))
end tell

Or perhaps more coolly presented: :slight_smile:

use AppleScript version "2.4" -- OS X 10.10 (Yosemite) or later
use framework "Foundation"
use scripting additions

set WifiStats to "		agrCtlRSSI: -29 
		agrExtRSSI: 0
		agrCtlNoise: -87
		agrExtNoise: 0
		state: running
		op mode: station 
		lastTxRate: 59
		maxRate: 72  
		lastAssocStatus: 0
		802.11 auth: open
		link auth: wpa2-psk
		BSSID: 8:78:8:0:aa:5c
		SSID: AndroidAP
		MCS: 6
 channel: 6"

set regex to current application's NSRegularExpressionSearch
set patternsAndReplacements to {¬
	{"(?# Trim leading and trailing white space and empty lines.)(?m)^\\s++|\\h++$|\\s++$", ""}, ¬
	{"(?# Enbar labels and enquote values.)(?m)^([^:]++):\\h++(.++)", "|$1|: \"$2\""}, ¬
	{"(?# Unenquote number values.)\"([0-9.-]++)\"", "$1"}, ¬
	{"(?# Replace internal line endings with commas.)\\R++", ", "} ¬
		}
tell (current application's class "NSMutableString"'s stringWithString:(WifiStats))
	repeat with this in patternsAndReplacements
		(its replaceOccurrencesOfString:(this's beginning) withString:(this's end) options:(regex) range:({0, its |length|()}))
	end repeat
	return my (run script ("{" & it & "}"))
end tell

I can confirm: it is very fast color=blue[/color] + returns numbers in numbers format :slight_smile:

I ran timing tests to verify the above. My computer is a 4-year-old Mac mini running Monterey and is neither fast nor slow by current standards. I used Script Geek and the results are in milliseconds:

SCRIPT - 1 ITERATION - 10 ITERATIONS

CK (JXA) - 4 - 45

peavine (ASOBJ) - 4 - 41

Nigel (first ASOBJC) - 2 - 10

IMO, these timing results are sufficiently close that other factors should probably determine which is used.

You yourself posted the excerpt below entitled From notes accompanying AS version 1.1, which proves (in the words of someone who clearly was an engineer on the team that developed AppleScript) that they are, and therefore still remain, a part of AppleScript.

I only post using my own name.

Nigel, this is not acceptable. I already had to ask another user to refrain from targeting personal comments at me (or anyone else), but that I now need to ask that this same code of conduct be adhered to by a moderator is disgraceful.

Please do not throw personal insults at me (or other users). I understand that, for whatever reason, my disagreeing with you appears to upset you. But this is not ok.

I’m familiar with the philosophy. I began my programming career somewhat more than a quarter of a century ago. I have explained it in previous posts, which I seem to recall you also took issue with.

And, no, my convoluted script doesn’t depend on them.

Again, not acceptable. I get it, you don’t like me. I don’t care. Find a better way to compose yourself or leave me alone.

I am aware I have certain communication issues that cause me to be interpreted as blunt, rude, or “a smartarse”. I am autistic, and this is something I continually work to improve, but it’s also not something I easily get right. I’m fine with criticisms being made about how I write in way that’s designed to be helpful and provide a way for me to learn, but you don’t know me as a person, and you don’t have the right to label me however you see fit just because you’re in a bad mood.

Am I being clear about this ?

So it seems that when a moderator doesn’t agree with one of the members, the thing to do is to try and attack them in an oblique fashion ? My name was Christofer, but it was changed long ago (by deed poll) to CK. There are some social media sites where this name is too short to be accepted, and I’ve been forced to retain my old name. I also use my old name in some professional contexts. But, frankly, none of this is any of your business, and I’m confident Mark does not behave in the manner that I’m seeing here.

No. You’re right. They’re completely unnecessary. I worked that out when I finally got round to de-obfuscating the script properly. (See post #24.)

As the moderator, it’s not for me to like or dislike anyone on this forum, any more than it is for you to tell me how to moderate it. That I do dislike.

My main concerns when moderating include:

• People asking for help here should be given accurate information that they can both use in the immediate context of their enquiry and understand well enough to be able use or adapt for use elsewhere if need be. Similarly, people reading posts in order to increase their AppleScript knowledge shouldn’t be handed stuff that might lead them astray later. This isn’t always possible, of course, but it should be the ambition. Obfuscated and uncommented scripts unnecessarily employing hacks and obsolete, long-undocumented features don’t come anywhere near achieving this.

• Posters with obvious talent and/or enthusiasm for AppleScript and MacScripter should be encouraged and given the best possible guidance — hopefully without stifling their own creativity. They should also be able to enjoy visiting and contributing to MacScripter without fear of their contributions being mocked (although constructive criticism is of course always welcome) or of being abused themselves. (See Posting Guidelines.) The spectacle of occasional visitors insisting that black is white, condescendingly referring to people’s comfort zones, and then laying down the law themselves about what is or is not acceptable is something we could all do without.

• MacScripter’s very few Rules and Posting Guidlines apply to everyone. I didn’t write them. I’ve just been landed with the job of moderating. If I see rules apparently being flouted by someone who’s also being difficult in the fora, it very much is my business.

Matters that simply boil down to common sense, such as not cluttering the fora with unnecessary replies to queries posted many years before, are, according to my brief, decided at my own discretion.

If you have issues with any of this, refer them to Mark. I’ll refer this topic to him myself so that he can remove my moderator status should he deem it necessary. Meanwhile this topic is now locked.