Page 1 of 1

Are you using NSTask for runprogram on mac?

Posted: Mon May 13, 2024 2:17 pm
by Piero

Code: Select all

; I hope this may help fix the bug

Procedure.s QuoteString(str$)
   ProcedureReturn "'" + ReplaceString(str$, "'", "'\''") + "'"
EndProcedure

Procedure simpleRunProgramforMac(stdin.s, workdir.s = "")
   Protected path.s = "/bin/zsh"
   stdin = "zsh -c " + QuoteString(stdin) ; "zsh -lc " for login shell using ~/.zprofile
   Protected task = CocoaMessage(0,CocoaMessage(0,0,"NSTask alloc"),"init")
   CocoaMessage(0,task,"setLaunchPath:$",@path)
   If workdir
      workdir = QuoteString(workdir) ; not tested
      CocoaMessage(0,task,"setCurrentDirectoryPath:$",@workdir)
   EndIf
   Protected writePipe = CocoaMessage(0,0,"NSPipe pipe")
   Protected writeHandle = CocoaMessage(0,writePipe,"fileHandleForWriting")
   CocoaMessage(0,task,"setStandardInput:",writePipe)
   Protected string = CocoaMessage(0,0,"NSString stringWithString:$",@stdin)
   Protected stringData = CocoaMessage(0,string,"dataUsingEncoding:",#NSUTF8StringEncoding)
   CocoaMessage(0,task,"launch")
   CocoaMessage(0,writeHandle,"writeData:",stringData)
   CocoaMessage(0,writeHandle,"closeFile")
   CocoaMessage(0,task,"release")
EndProcedure

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

simpleRunProgramforMac(~"osascript -e 'tell app \"finder\" to beep'")

(shortened) args version

Posted: Fri May 17, 2024 9:20 am
by Piero

Code: Select all

; using args:

Procedure RunProgramforMac(param.s)
   Protected argsArray = CocoaMessage(0,0,"NSArray arrayWithObject:$",@"-c") ; -lc
   argsArray = CocoaMessage(0,argsArray,"arrayByAddingObject:$",@param)
   Protected task = CocoaMessage(0,CocoaMessage(0,0,"NSTask alloc"),"init")
   CocoaMessage(0,task,"setLaunchPath:$",@"/bin/zsh") ; zsh
   CocoaMessage(0,task,"setArguments:",argsArray)
   CocoaMessage(0,task,"launch")
   CocoaMessage(0,task,"release")
EndProcedure

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

RunProgramforMac(~"osascript -e 'tell app \"finder\" to beep'")

Do shell script objc

Posted: Fri May 17, 2024 11:29 am
by Piero
Found on web; dedicated to mk and deseven (if this can be useful):

Code: Select all

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

-- simple call
set x1 to my doShellScript:"echo 'Hello world'"
-- calling tool directly instead of via /bin/sh
set x2 to my runTool:"/usr/bin/tr" withArgs:{"a", "o"} stdInput:"bad cat" envDict:(missing value) useLocale:false useReturns:false stripLast:true resultType:(text)
-- an old Nigel Garvey example revisited out of season, showing use of locale
-- set x3 to my doShellScriptLocalized:("echo 'x-93yß<⌘wß⌘-r7ßßq' | sed 'y?<379-x⌘qß?Np!paHe" & character id 127863 & "Y ?'")
-- do comparison
set thePath to POSIX path of (choose file)
set x4 to do shell script "cat " & quoted form of thePath
set x5 to my doShellScript:("cat " & quoted form of thePath)
return x4 = x5


-- This matches plain 'do shell script'
on doShellScript:theCommand
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:false useReturns:true stripLast:true resultType:(text)
end doShellScript:

-- This matches 'do shell script without altering line endings'
on doShellScriptNoAltering:theCommand
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:false useReturns:false stripLast:false resultType:(text)
end doShellScriptNoAltering:

-- This matches 'do shell script without altering line endings' but still strips the trailing linefeed
on doShellScriptWithLFs:theCommand
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:false useReturns:false stripLast:true resultType:(text)
end doShellScriptWithLFs:

--- This lets you configure line ending behavior and the result type
-- resultType: string = matches osax; data = like osax "as data"; "NSString" = NSString if UTF-8, else unmodified NSData; "NSData" = unmodified NSData; "Base-64" = unmodified data as Base-64 string
on doShellScript:theCommand useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:false useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
end doShellScript:useReturns:stripLast:resultType:

-- This lets you configure most things
-- resultType: string = matches osax; data = like osax "as data"; "NSString" = NSString if UTF-8, else unmodified NSData; "NSData" = unmodified NSData; "Base-64" = unmodified data as Base-64 string
-- useReturns and stripLast are booleans; envDict is a record/NSDictionary or missing value; stdInput is a string/NSString or missing value
-- localeValue: false or 0 = ignore; true or 1 = set LANG, LC_CTYPE, and LC_COLLATE to current locale; 3 = sets LC_ALL to current locale
on doShellScript:theCommand stdInput:theInput envDict:envDict useLocale:localeValue useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:theInput envDict:envDict useLocale:localeValue useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
end doShellScript:stdInput:envDict:useLocale:useReturns:stripLast:resultType:

-- This matches plain 'do shell script' but sets LANG, LC_CTYPE, and LC_COLLATE to current locale
on doShellScriptLocalized:theCommand
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:true useReturns:true stripLast:true resultType:(text)
end doShellScriptLocalized:

-- This matches 'do shell script without altering line endings' but sets LANG, LC_CTYPE, and LC_COLLATE to current locale
on doShellScriptNoAlteringLocalized:theCommand
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:true useReturns:false stripLast:false resultType:(text)
end doShellScriptNoAlteringLocalized:

-- This matches 'do shell script without altering line endings' but still strips the trailing linefeed and uses sets LANG, LC_CTYPE, and LC_COLLATE to current locale
on doShellScriptWithLFsLocalized:theCommand
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:true useReturns:false stripLast:true resultType:(text)
end doShellScriptWithLFsLocalized:

-- This lets you configure line ending behavior, and whether to return an NSString or text; sets LANG, LC_CTYPE, and LC_COLLATE to current locale
-- resultType: 1 = string, 2 = NSString, 3 = NSData, 4 = Base-64 string; useReturns and stripLast are booleans
on doShellScriptLocalized:theCommand useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
	return my runTool:"/bin/sh" withArgs:{"-c", theCommand} stdInput:(missing value) envDict:(missing value) useLocale:true useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
end doShellScriptLocalized:useReturns:stripLast:resultType:

-- This is the "master" handler called by the above. You can also call it directly, to address tools directly or to use a different shell. In this case you pass the arguments as a list, and you must use the path to the tool.
-- For example: runTool:"/usr/bin/tr" withArgs:{"a", "o"} stdInput:"bad cat" envDict:(missing value) useLocale:false useReturns:false stripLast:true resultType:text
-- resultType: string = matches osax; data = like osax "as data"; "NSString" = NSString if UTF-8, else unmodified NSData; "NSData" = unmodified NSData; "Base-64" = unmodified data as Base-64 string
-- useReturns and stripLast are booleans; envDict is a record/NSDictionary or missing value; stdInput is a string/NSString or missing value
-- localeValue: false or 0 = ignore; true or 1 = set LANG, LC_CTYPE, and LC_COLLATE to current locale; 3 = sets LC_ALL to current locale
on runTool:thePath withArgs:theArgList stdInput:theInput envDict:envDict useLocale:localeValue useReturns:useReturnsFlag stripLast:stripLastFlag resultType:resultType
	set envMutDict to current application's NSMutableDictionary's dictionary()
	if localeValue as integer > 0 then
		set localeName to current application's NSLocale's currentLocale()'s localeIdentifier()
		if localeValue as integer = 1 then
			envMutDict's addEntriesFromDictionary:{LANG:localeName, LC_CTYPE:localeName, LC_COLLATE:localeName}
		else
			envMutDict's setObject:localeName forKey:"LC_ALL"
		end if
	end if
	if envDict is not missing value then envMutDict's addEntriesFromDictionary:envDict
	-- create a pipe for standard output, then get a file handle for reading from it
	set outPipe to current application's NSPipe's pipe()
	set outFileHandle to outPipe's fileHandleForReading()
	-- create a pipe for standard error
	set errPipe to current application's NSPipe's pipe()
	-- make task, set its properties
	set theTask to current application's NSTask's new()
	set outData to current application's NSMutableData's |data|()
	theTask's setLaunchPath:thePath
	theTask's setArguments:theArgList
	theTask's setStandardOutput:outPipe
	theTask's setStandardError:errPipe
	if envMutDict's |count|() > 0 then theTask's setEnvironment:envMutDict
	if theInput is missing value then
		theTask's |launch|()
	else
		-- convert the input to data
		set theInput to current application's NSString's stringWithString:theInput
		set theInputData to theInput's dataUsingEncoding:(current application's NSUTF8StringEncoding)
		-- create a pipe for standard input, then get a file handle for writing to it
		set inputPipe to current application's NSPipe's pipe()
		set inputFileHandle to inputPipe's fileHandleForWriting()
		-- connect pipe to task and launch it
		theTask's setStandardInput:inputPipe
		theTask's |launch|()
		-- write to standard input
		inputFileHandle's writeData:theInputData
		inputFileHandle's closeFile()
	end if
	-- collect standard output while it's running
	repeat while theTask's isRunning() as boolean
		set newData to outFileHandle's availableData()
		if newData is not missing value then outData's appendData:newData
	end repeat
	-- check if all went OK
	set taskStatus to theTask's |terminationStatus|() as integer
	if taskStatus = 0 then -- all OK
		set newData to outFileHandle's readDataToEndOfFile()
		outFileHandle's closeFile()
		if newData is not missing value then outData's appendData:newData
		if outData's |length|() < 1 then
			-- some tools output to standard error regardless, so check there instead
			set outData to errPipe's fileHandleForReading()'s readDataToEndOfFile()
			errPipe's fileHandleForReading()'s closeFile()
		end if
		if resultType = "NSData" then return outData's |copy|()
		if resultType = data then -- convert to AS data
			set theCode to current application's NSHFSTypeCodeFromFileType("'rdat'")
			return (current application's NSAppleEventDescriptor's descriptorWithDescriptorType:theCode |data|:outData) as data
		end if
		if resultType = "Base-64" then return (outData's base64EncodedStringWithOptions:0) as text
		set theString to current application's NSString's alloc()'s initWithData:outData encoding:(current application's NSUTF8StringEncoding)
		if theString is missing value then -- not valid UTF-8
			if resultType = text then
				-- Output as MacRoman, as 'do shell script' does. Awful, but compatible...
				set theString to current application's NSString's alloc()'s initWithData:outData encoding:(current application's NSMacOSRomanStringEncoding)
				-- if 'altering line endings' is true, 'do shell script' also changes CRLF to LF, so to match...
				if useReturnsFlag then set theString to theString's stringByReplacingOccurrencesOfString:(return & linefeed) withString:return
			else -- resultType "NSString", fall back to raw data
				return outData's |copy|()
			end if
		else if stripLastFlag then -- only if it's UTF-8
			if (theString's hasSuffix:linefeed) as boolean then set theString to theString's substringToIndex:((theString's |length|()) - 1)
		end if
		if useReturnsFlag then set theString to theString's stringByReplacingOccurrencesOfString:linefeed withString:return
		if resultType is "NSString" then return theString
		return theString as text
	else -- there was an error, so get the data from standardError
		set errData to errPipe's fileHandleForReading()'s readDataToEndOfFile()
		errPipe's fileHandleForReading()'s closeFile()
		set theString to current application's NSString's alloc()'s initWithData:errData encoding:(current application's NSUTF8StringEncoding)
		if theString is missing value then
			-- match 'do shell script' fallback
			set theString to current application's NSString's alloc()'s initWithData:outData encoding:(current application's NSUTF8StringEncoding)
		end if
		if theString is missing value then
			error number taskStatus
		else
			error (theString as string) number taskStatus
		end if
	end if
end runTool:withArgs:stdInput:envDict:useLocale:useReturns:stripLast:resultType: