Is there a better way than this to run Applescripts? (without using Cocoa)

Mac OSX specific forum
User avatar
Piero
Addict
Addict
Posts: 885
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Is there a better way than this to run Applescripts? (without using Cocoa)

Post by Piero »

Code: Select all

; making a temporary file for osascript, because there's a problem with
; RunProgram's Parameter and double quotes ( https://www.purebasic.fr/english/viewtopic.php?t=81520 )
scriptPath$ = GetTemporaryDirectory() + "tmpascr"
scriptFile = CreateFile(#PB_Any, scriptPath$)
if scriptFile
   ; " \n " (\n enclosed in spaces) only for better readability:
   scriptSrc$ = ~"tell app \"Finder\" \n beep \n delay 1 \n beep \n end tell"
   WriteString(scriptFile, scriptSrc$) : CloseFile(scriptFile)
   RunProgram("osascript", scriptPath$, "", #PB_Program_Wait)
   RunProgram("rm", scriptPath$, "") ; delete tmp file
EndIf
Thanks!
Piero
Last edited by Piero on Wed May 03, 2023 4:52 am, edited 1 time in total.
User avatar
mk-soft
Always Here
Always Here
Posts: 6226
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Is there a better way than this to run Applescripts? (without using Cocoa)

Post by mk-soft »

CocoaMessage is already the right way to be able to work with Objective-C.

You (only) have to resolve the Objective-C examples from the inside to the outside for CocoaMessage itself.
I have added the DumpObjectMethods, because sometimes the description differs from the real method names.

I have also reworked the script processing.

Code: Select all

;-TOP Dump Object Methods

; by mk-soft, 29.12.2019 - 06.11.2022, v1.08.2

Structure ArrayOfMethods
  i.i[0]
EndStructure

ImportC ""
  class_copyMethodList(*Class, *p_methodCount)
  ; -> An array of pointers of type Method describing
  ;    the instance methods implemented by the class
  ;    Any instance methods implemented by superclasses are Not included
  ;    You must free the array with free()
  class_getName(*Class) ; -> UnsafePointer<Int8> -> *string
  sel_getName(*Selector); -> const char *
  method_getName(*Method) ; -> Selector
  method_getTypeEncoding(*Method) ; -> const char *
  method_getReturnType(*Method, *dst, dst_len) ; -> void
  method_getNumberOfArguments(*Method)         ; -> unsigned int
  method_getArgumentType(*Method, index, *dst, dst_len) ; -> void
  
  NSGetSizeAndAlignment(*StringPtr, *p_size, *p_align) 
  ; -> const char *
  ;    Obtains the actual size and the aligned size of an encoded type.
EndImport

; ----

Procedure.s GetArgumentType(*String)
  Protected r1.s, arg.s, size.i, ofs.i
  
  arg = PeekS(*String, -1, #PB_UTF8)
  r1 + arg + " - "
  If Left(arg, 1) = "^"
    r1 + "A pointer to type of "
    arg = Mid(arg, 2)
  EndIf
  Select arg
    Case "c" : r1 + "A char "
    Case "i" : r1 + "An int "
    Case "s" : r1 + "A short "
    Case "l" : r1 + "A long "
    Case "q" : r1 + "A long long"
    Case "C" : r1 + "An unsigned char "
    Case "I" : r1 + "An unsigned int "
    Case "S" : r1 + "An unsigned short "
    Case "L" : r1 + "An unsigned long "
    Case "Q" : r1 + "An unsigned long long "
    Case "f" : r1 + "A float "
    Case "d" : r1 + "A double "
    Case "B" : r1 + "A C++ bool Or a C99 _Bool "
    Case "v" : r1 + "A void"
    Case "*" : r1 + "A character string (char *) "
    Case "@" : r1 + "An object (whether statically typed Or typed id) "
    Case "#" : r1 + "A class object (Class) "
    Case ":" : r1 + "A method selector (SEL) "
    Default:
      NSGetSizeAndAlignment(*String, @size, @ofs)
      r1 + "[" + Str(size) + " bytes]"
  EndSelect
  If Right(arg, 1) = "?"
    r1 + "An unknown type (e.g. function pointer)"
  EndIf
  ProcedureReturn r1
EndProcedure

; ----

Procedure.s DumpObjectMethods(*Object, SuperLevel = 0, HidePrivate = #True, ShowEncoding = #False, FirstArgument = 2)
  Protected r1.s, i, c, n, methodCount, Method.s
  Protected *Class, *SuperClass, *Method, *Methods.ArrayOfMethods
  Protected *String
  
  *Class = object_getclass_(*Object)
  If *Class
    *String = AllocateMemory(1024)
    r1 = PeekS(class_getName(*Class), -1, #PB_UTF8)
    If SuperLevel
      For i = 1 To SuperLevel
        *SuperClass = class_getsuperclass_(*Class)
        If *SuperClass
          *Class = *SuperClass
          r1 + " -> " + PeekS(class_getName(*Class), -1, #PB_UTF8)
        Else
          Break
        EndIf
      Next
    EndIf
    *Methods = class_copyMethodList(*Class, @methodCount)
    r1 + #LF$ + #LF$ + "Count of Methods: " + methodCount + #LF$ + #LF$
    For i = 0 To methodCount - 1
      *Method = *Methods\i[i];
      Method = PeekS(sel_getName(method_getName(*Method)), -1, #PB_UTF8)
      If HidePrivate And Left(Method, 1) = "_"
        Continue
      EndIf
      r1 + "Method " + Method + #LF$
      If ShowEncoding
        r1 + " * Encoding " + PeekS(method_getTypeEncoding(*Method), -1, #PB_UTF8) + #LF$
      EndIf
      method_getReturnType(*Method, *String, 1024)
      r1 + " -- ReturnType = " + GetArgumentType(*String) + #LF$
      c = method_getNumberOfArguments(*Method)
      For n = FirstArgument To c - 1
        method_getArgumentType(*Method, n, *String, 1024)
        r1 + " -- Argument " + Str(n - FirstArgument + 1) + " = " + GetArgumentType(*String) + #LF$
      Next
      r1 + #LF$
    Next
    r1 + "End Class" + #LF$ + #LF$
    If *Methods
      free_(*Methods)
    EndIf
    FreeMemory(*String)
  Else
    r1 = "Object is nil" + #LF$
  EndIf
  ProcedureReturn r1
EndProcedure

; ****

Macro CocoaString(NSString)
  PeekS(CocoaMessage(0, NSString, "UTF8String"), -1, #PB_UTF8)
EndMacro

Procedure.s AppleScript2(Script.s)
  Protected retVal.s, strVal, numItems, i
  Protected obj_pool
  Protected obj_AppleScript, obj_Script, obj_EventDescriptor, obj_Descriptor
  
  obj_pool = CocoaMessage(0, 0, "NSAutoreleasePool new")
  
  obj_AppleScript = CocoaMessage(0, 0, "NSAppleScript new")
  
  Debug DumpObjectMethods(obj_AppleScript, 0) ; Class
  Debug DumpObjectMethods(obj_AppleScript, 1) ; SuperClass
  
  If obj_AppleScript
  
    obj_Script = CocoaMessage(0, obj_AppleScript, "initWithSource:$", @Script)
    If obj_Script
      obj_EventDescriptor = CocoaMessage(0, obj_Script, "executeAndReturnError:", #nil)
      If obj_EventDescriptor
        numItems = CocoaMessage(0, obj_EventDescriptor, "numberOfItems")
        If numItems
          For i = 1 To numItems
            obj_Descriptor = CocoaMessage(0, obj_EventDescriptor, "descriptorAtIndex:", i)
            strVal = CocoaMessage(0, obj_Descriptor, "stringValue")
            If strVal
              retVal + CocoaString(strVal)
              If i <> numItems : retVal + #LF$ : EndIf
            EndIf
          Next
        Else
          strVal = CocoaMessage(0, obj_EventDescriptor, "stringValue")
          If strVal
            retVal = CocoaString(strVal)
          EndIf
        EndIf
      EndIf
    EndIf
  EndIf
  
  CocoaMessage(0, obj_pool, "release")
  
  ProcedureReturn retVal 
EndProcedure

CompilerIf #PB_Compiler_IsMainFile
  
  Define script.s, result.s
  
  ;script = "tell application " + Chr(34) + "Finder" + Chr(34) + " To get the name of every item in the desktop"
  script = ~"tell app \"Finder\" \n beep \n delay 1 \n beep \n end tell"
  result = AppleScript2(script)
  
  Debug result
  
CompilerEndIf
Last edited by mk-soft on Tue May 02, 2023 7:13 pm, edited 2 times in total.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
Piero
Addict
Addict
Posts: 885
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Is there a better way than this to run Applescripts? (without using Cocoa)

Post by Piero »

mk-soft wrote: Tue May 02, 2023 6:44 pm CocoaMessage is already the right way to be able to work with Objective-C.
Interesting, Thanks!
Last edited by Piero on Tue May 02, 2023 9:08 pm, edited 2 times in total.
User avatar
mk-soft
Always Here
Always Here
Posts: 6226
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Is there a better way than this to run Applescripts? (without using Cocoa)

Post by mk-soft »

Why I use my own NSPool.

Purebasic uses its own NSPool internally in the MainScope. This is cleaned up with WaitWindowsEvent.
But sometimes a procedure creates a lot of objects. Apple recommends creating an extra NSPool for this.
Or if you want to use CocoaMessage in threads, you must create your own NSPool, otherwise there will be conflicts with the NSPool of the MainScope (crash).
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
mk-soft
Always Here
Always Here
Posts: 6226
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Is there a better way than this to run Applescripts? (without using Cocoa)

Post by mk-soft »

Now with ErrorInfo

Code: Select all

;-TOP Dump Object Methods

; by mk-soft, 29.12.2019 - 06.11.2022, v1.08.2

Structure ArrayOfMethods
  i.i[0]
EndStructure

ImportC ""
  class_copyMethodList(*Class, *p_methodCount)
  ; -> An array of pointers of type Method describing
  ;    the instance methods implemented by the class
  ;    Any instance methods implemented by superclasses are Not included
  ;    You must free the array with free()
  class_getName(*Class) ; -> UnsafePointer<Int8> -> *string
  sel_getName(*Selector); -> const char *
  method_getName(*Method) ; -> Selector
  method_getTypeEncoding(*Method) ; -> const char *
  method_getReturnType(*Method, *dst, dst_len) ; -> void
  method_getNumberOfArguments(*Method)         ; -> unsigned int
  method_getArgumentType(*Method, index, *dst, dst_len) ; -> void
  
  NSGetSizeAndAlignment(*StringPtr, *p_size, *p_align) 
  ; -> const char *
  ;    Obtains the actual size and the aligned size of an encoded type.
EndImport

; ----

Procedure.s GetArgumentType(*String)
  Protected r1.s, arg.s, size.i, ofs.i
  
  arg = PeekS(*String, -1, #PB_UTF8)
  r1 + arg + " - "
  If Left(arg, 1) = "^"
    r1 + "A pointer to type of "
    arg = Mid(arg, 2)
  EndIf
  Select arg
    Case "c" : r1 + "A char "
    Case "i" : r1 + "An int "
    Case "s" : r1 + "A short "
    Case "l" : r1 + "A long "
    Case "q" : r1 + "A long long"
    Case "C" : r1 + "An unsigned char "
    Case "I" : r1 + "An unsigned int "
    Case "S" : r1 + "An unsigned short "
    Case "L" : r1 + "An unsigned long "
    Case "Q" : r1 + "An unsigned long long "
    Case "f" : r1 + "A float "
    Case "d" : r1 + "A double "
    Case "B" : r1 + "A C++ bool Or a C99 _Bool "
    Case "v" : r1 + "A void"
    Case "*" : r1 + "A character string (char *) "
    Case "@" : r1 + "An object (whether statically typed Or typed id) "
    Case "#" : r1 + "A class object (Class) "
    Case ":" : r1 + "A method selector (SEL) "
    Default:
      NSGetSizeAndAlignment(*String, @size, @ofs)
      r1 + "[" + Str(size) + " bytes]"
  EndSelect
  If Right(arg, 1) = "?"
    r1 + "An unknown type (e.g. function pointer)"
  EndIf
  ProcedureReturn r1
EndProcedure

; ----

Procedure.s DumpObjectMethods(*Object, SuperLevel = 0, HidePrivate = #True, ShowEncoding = #False, FirstArgument = 2)
  Protected r1.s, i, c, n, methodCount, Method.s
  Protected *Class, *SuperClass, *Method, *Methods.ArrayOfMethods
  Protected *String
  
  *Class = object_getclass_(*Object)
  If *Class
    *String = AllocateMemory(1024)
    r1 = PeekS(class_getName(*Class), -1, #PB_UTF8)
    If SuperLevel
      For i = 1 To SuperLevel
        *SuperClass = class_getsuperclass_(*Class)
        If *SuperClass
          *Class = *SuperClass
          r1 + " -> " + PeekS(class_getName(*Class), -1, #PB_UTF8)
        Else
          Break
        EndIf
      Next
    EndIf
    *Methods = class_copyMethodList(*Class, @methodCount)
    r1 + #LF$ + #LF$ + "Count of Methods: " + methodCount + #LF$ + #LF$
    For i = 0 To methodCount - 1
      *Method = *Methods\i[i];
      Method = PeekS(sel_getName(method_getName(*Method)), -1, #PB_UTF8)
      If HidePrivate And Left(Method, 1) = "_"
        Continue
      EndIf
      r1 + "Method " + Method + #LF$
      If ShowEncoding
        r1 + " * Encoding " + PeekS(method_getTypeEncoding(*Method), -1, #PB_UTF8) + #LF$
      EndIf
      method_getReturnType(*Method, *String, 1024)
      r1 + " -- ReturnType = " + GetArgumentType(*String) + #LF$
      c = method_getNumberOfArguments(*Method)
      For n = FirstArgument To c - 1
        method_getArgumentType(*Method, n, *String, 1024)
        r1 + " -- Argument " + Str(n - FirstArgument + 1) + " = " + GetArgumentType(*String) + #LF$
      Next
      r1 + #LF$
    Next
    r1 + "End Class" + #LF$ + #LF$
    If *Methods
      free_(*Methods)
    EndIf
    FreeMemory(*String)
  Else
    r1 = "Object is nil" + #LF$
  EndIf
  ProcedureReturn r1
EndProcedure

; ****

Macro CocoaString(NSString)
  PeekS(CocoaMessage(0, NSString, "UTF8String"), -1, #PB_UTF8)
EndMacro

Procedure.s AppleScript(Script.s)
  Protected retVal.s, strVal, numItems, i
  Protected obj_pool
  Protected obj_AppleScript, obj_Script, obj_EventDescriptor, obj_Descriptor, obj_ErrorInfo
  
  obj_pool = CocoaMessage(0, 0, "NSAutoreleasePool new")
  
  obj_AppleScript = CocoaMessage(0, 0, "NSAppleScript new")
  
  Debug DumpObjectMethods(obj_AppleScript, 0) ; Class
  Debug DumpObjectMethods(obj_AppleScript, 1) ; SuperClass
  
  If obj_AppleScript
  
     obj_Script = CocoaMessage(0, obj_AppleScript, "initWithSource:$", @Script)
    If obj_Script
      obj_EventDescriptor = CocoaMessage(0, obj_Script, "executeAndReturnError:", @obj_ErrorInfo)
      If obj_EventDescriptor
        numItems = CocoaMessage(0, obj_EventDescriptor, "numberOfItems")
        If numItems
          For i = 1 To numItems
            obj_Descriptor = CocoaMessage(0, obj_EventDescriptor, "descriptorAtIndex:", i)
            strVal = CocoaMessage(0, obj_Descriptor, "stringValue")
            If strVal
              retVal + CocoaString(strVal)
              If i <> numItems : retVal + #LF$ : EndIf
            EndIf
          Next
        Else
          strVal = CocoaMessage(0, obj_EventDescriptor, "stringValue")
          If strVal
            retVal = CocoaString(strVal)
          EndIf
        EndIf
      Else
        If obj_ErrorInfo
          strVal = CocoaMessage(0, obj_ErrorInfo, "objectForKey:$", @"NSAppleScriptErrorMessage")
          If strVal
            retVal = "[ErrorInfo] " + CocoaString(strVal)
          EndIf
        EndIf
      EndIf
    EndIf
  EndIf
  
  CocoaMessage(0, obj_pool, "release")
  
  ProcedureReturn retVal 
EndProcedure

CompilerIf #PB_Compiler_IsMainFile
  
  Define script.s, result.s
  
  ;script = "tell application " + Chr(34) + "Finder" + Chr(34) + " To get the name of every item in the desktop"
  ;script = ~"tell app \"Finder\" \n beep \n delay 1 \n beep \n end tell"
  script = ~"tellx app \"Finder\" \n beep \n delay 1 \n beep \n end tell" ; Error
  result = AppleScript(script)
  If Left(result, 11) = "[ErrorInfo]"
    Debug result
  Else
    Debug result
  EndIf
  
CompilerEndIf
Last edited by mk-soft on Tue May 02, 2023 9:12 pm, edited 1 time in total.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
Piero
Addict
Addict
Posts: 885
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Is there a better way than this to run Applescripts? (without using Cocoa)

Post by Piero »

mk-soft wrote: Tue May 02, 2023 7:04 pm Why I use my own NSPool.

Purebasic uses its own NSPool internally in the MainScope. This is cleaned up with WaitWindowsEvent.
But sometimes a procedure creates a lot of objects. Apple recommends creating an extra NSPool for this.
Or if you want to use CocoaMessage in threads, you must create your own NSPool, otherwise there will be conflicts with the NSPool of the MainScope (crash).
Again, interesting, Thanks!
I just wanted to save some lines of code ;)
User avatar
Piero
Addict
Addict
Posts: 885
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Is there a better way than this to run Applescripts? (without using Cocoa)

Post by Piero »

Piero wrote: Tue May 02, 2023 3:56 pmThanks!
Yes, You Silly Italian:

Code: Select all

Shstr$ = "osascript"
; Shstr$ + ~" -e ''"

Shstr$ + ~" -e 'try'"
Shstr$ + ~" -e 'tell app \"finder\"'"
Shstr$ + ~" -e 'activate'"
Shstr$ + ~" -e 'display dialog (name of 1st item of desktop) as text'"
Shstr$ + ~" -e 'return name of 1st item of desktop'" ; return not really needed here...
Shstr$ + ~" -e 'end'" ; compiles to "end tell"
Shstr$ + ~" -e 'on error e number n'"
Shstr$ + ~" -e 'beep'"
Shstr$ + ~" -e 'return \"AsError:\r\" & e'" ; cancel button pressed
Shstr$ + ~" -e 'end'" ; compiles to "end try"

Output$ = ""
shell = RunProgram("/bin/sh","","",
   #PB_Program_Open|#PB_Program_Write|#PB_Program_Read)
If shell
   WriteProgramStringN(shell,Shstr$)
   WriteProgramData(shell,#PB_Program_Eof,0)
   While ProgramRunning(shell)
      If AvailableProgramOutput(shell)
         Output$ + ReadProgramString(shell) ; + Chr(13)
      EndIf
   Wend
   CloseProgram(shell)
EndIf

MessageRequester("Output", Output$)

-- AppleScript to make things easier:

-- Remember to remove comments from the Applescript code you copy

set c to the clipboard as text

set pbt to ""
repeat with i in paragraphs of c
    if i as text is not "" then
        set i to replace_chars(i, tab, "") -- editor must use tabs to indent...
        
set i to replace_chars(i, "\\\"", "\\\\\"")
        
set i to replace_chars(i, "\"", "\\\"")
        
set i to "Shstr$ + ~\" -e '" & i & "'\"" -- Shstr$ PB String
        
set pbt to pbt & i & "\r"
    end if
end repeat

display dialog "Result:" buttons {"OK"} default button "OK" default answer pbt
-- set the clipboard to pbt

on replace_chars(this_text, search_string, replacement_string)
    set AppleScript's text item delimiters to the search_string
    
set the item_list to every text item of this_text
    
set AppleScript's text item delimiters to the replacement_string
    
set this_text to the item_list as string
    
set AppleScript's text item delimiters to ""
    
return this_text
end replace_chars
User avatar
Piero
Addict
Addict
Posts: 885
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Is there a better way than this to run Applescripts? (without using Cocoa)

Post by Piero »

 
I slightly edited this post to a more decent (and clear) comment removal

-- Version 0.001a3

-- Better compile your Applescript code, before copying it…
set c to the clipboard as string

property PB_S : "Shstr$" -- comment line below to skip dialog
set PB_S to text returned of (display dialog "PB Var$?" buttons {"OK"} default button "OK" default answer PB_S)

set pbt to ""
set rc to false -- false = do not remove comments
try
    display dialog "Remove -- Comments?\n\nAVOID -- in strings!!!\n\nCancel to keep"
    
set rc to true
end try
repeat with i in paragraphs of c
    set i to i as string
    
set i to replace_chars(i, tab, "") -- editor MUST use TABS to indent...
    
if rc then --remove comments
        set o to offset of "--" in i
        
if o > 1 then
            set i to (characters 1 thru (o - 2) of i) as string
        else if o = 1 then
            set i to ""
        end if
    end if
    
if i is not "" then
        set i to replace_chars(i, "\\\"", "\\\\\"")
        
set i to replace_chars(i, "\"", "\\\"")
        
set i to PB_S & " + ~\" -e '" & i & "'\""
        
set pbt to pbt & i & "\r"
    end if
end repeat -- set pbt to PB_S & " = \"osascript\"" & "\r" & pbt -- better reset on a PB "shell" procedure...

set dla to display dialog "Set the Clipboard to this? (you can modify before)" default answer pbt
set the clipboard to text returned of dla -- set the clipboard to pbt -- no modify

on replace_chars(this_text, search_string, replacement_string)
    set AppleScript's text item delimiters to the search_string
    
set the item_list to every text item of this_text
    
set AppleScript's text item delimiters to the replacement_string
    
set this_text to the item_list as string
    
set AppleScript's text item delimiters to ""
    
return this_text
end replace_chars
User avatar
Piero
Addict
Addict
Posts: 885
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Is there a better way than this to run Applescripts? (without using Cocoa)

Post by Piero »

Also see post above…

Code: Select all

; Version 0.001a5 - Minor recoding of errors catch/handle

EnableExplicit

#myAS_Err = "[AS_Error]" : #myAS_ErrSep = ":|:|:"
#myCatch = ~" -e 'on error e number n' -e 'beep'" + ; beeps
   ~" -e 'return \""+ #myAS_Err +~"\" & tab & n & tab & \""+#myAS_ErrSep+~"\" & e' -e 'end'"

Structure daResults
   Out.s
   Err.s
   ExC.w ; exit code
EndStructure

Global Sh.daResults, Shstr$, my_global_str$

Procedure Shell(ShellCommand$, AddReturns = #True)
   Protected Err$, tmper$, Output$, shell, Exc.w = -1 ; -1 on failed launch
   shell = RunProgram("/bin/sh","","",
      #PB_Program_Open|#PB_Program_Write|#PB_Program_Read|#PB_Program_Error )
   If shell
      WriteProgramStringN(shell,ShellCommand$)
      WriteProgramData(shell,#PB_Program_Eof,0)
      While ProgramRunning(shell)
         If AvailableProgramOutput(shell)
            Output$ + ReadProgramString(shell)
            If AddReturns : Output$ + Chr(13) : EndIf
         EndIf
         tmper$ = ReadProgramError(shell)
         If tmper$ : Err$ + tmper$ + Chr(13) : EndIf
      Wend
      Exc = ProgramExitCode(shell) : CloseProgram(shell)
   EndIf
   Sh\Out = Output$ : Sh\Err = Err$ : Sh\ExC = Exc
EndProcedure

Procedure simpleShell(ShellCommand$)
   Protected shell
   shell = RunProgram("/bin/sh","","", #PB_Program_Open|#PB_Program_Write)
   If shell
      WriteProgramStringN(shell,ShellCommand$)
      WriteProgramData(shell,#PB_Program_Eof,0)
      CloseProgram(shell)
   EndIf
EndProcedure

Procedure AppleScript(A_S$, wait = #False)
   If Left(LTrim(A_S$),2) <> "-e"
      If (wait) : Shell("osascript -e '"+A_S$+"'", #False) ; don't add Chr(13)
      Else : simpleShell("osascript -e '"+A_S$+"'") : EndIf
   Else
      If (wait) : Shell("osascript "+A_S$, #False) ; don't add Chr(13)
      Else : simpleShell("osascript "+A_S$) : Endif
   EndIf
EndProcedure

Procedure shErr(stop = #False)
   if sh\ExC or sh\Err
      Debug ~"\n\nsh error:"
      Debug sh\ExC ; sh exit code
      Debug sh\Err ; sh error
      If stop : End : EndIf
   EndIf
EndProcedure

Procedure AsErr(stop = #False)
   if FindString(sh\Out, #myAS_Err) = 1
      Debug ~"\n\nAppleScript error:"
      Debug StringField(sh\Out, 2, #TAB$) ; AppleScript exit code
      Debug StringField(sh\Out, 2, #TAB$ + #myAS_ErrSep) ; AppleScript error
      If stop : End : EndIf
   EndIf
EndProcedure

Procedure.s qq(shstop=#False, ascstop=#False) ; quick-quick TAB :P (my autocomplete is set to 2 chars)
   AppleScript(Shstr$, 1)
   Shstr$ = "" ; reset
   AsErr(ascstop) ; process Applescript error; AsErr() = don't stop PB in case of error, AsErr(1) stops
   shErr(shstop) ; process sh errors; shErr() = don't stop PB in case of error, shErr(1) stops
   ProcedureReturn sh\Out
EndProcedure

Macro asd
   MessageRequester("Applescript Output:", qq() + ~"\n\nClick OK to Continue…")
EndMacro

Procedure.s findersel() ; returns Finder selection as tab-separated paths
   Shstr$ + ~" -e 'tell app \"Finder\"'" +
      ~" -e 'set sel to selection' -e 'set outp to \"\"'" + ~" -e 'try' -e 'repeat with i in sel'" +
      ~" -e 'copy outp & POSIX path of (i as alias) & tab to outp' -e 'end'" +
      ~" -e 'set outp to (characters 1 thru -2 of outp) as string' -e 'end' -e 'outp' -e 'end'"
   ProcedureReturn qq()
EndProcedure

Prototype Function()

Runtime Procedure myDebug() ; will be 'run by name' below
   Debug my_global_str$
   my_global_str$ = "" ; just in case…
EndProcedure

Procedure processfilfol(procName$, FileS.s, fil, fol)
   Protected File$, siz, k, RunProc.Function = GetRuntimeInteger(procName$ + "()")
   Repeat
      File$ = StringField(FileS.s, k+1, #TAB$) ; index is 1 based
      If File$ <> ""
         siz=FileSize(File$)
         If (siz >= 0 and fil) or (siz < 0 and fol)
            my_global_str$ = File$
            RunProc() ; Runtime Procedure procName$
         EndIf
         k+1
      EndIf
   Until File$ = ""
EndProcedure

; ********************************************************************

AppleScript(~"say \"applescript\"") ; doesn't wait for results

Shstr$ + ~" -e 'try'" ; catch all errors
Shstr$ + ~" -e 'tell app \"finder\"'" ; "compiles" to: tell application "Finder"
Shstr$ + ~" -e 'activate'" ; or "launch", or even "run"…
Shstr$ + ~" -e 'display dialog \"Click Cancel for Applescript User Canceled Error\"'"
Shstr$ + ~" -e 'return (name of 1st item of desktop) as string'" ; return not really needed here…
Shstr$ + ~" -e 'end'" ; compiles to "end tell" (to Finder)
Shstr$ + ~" -e 'on error e number n'" ; better check for errors by number (because of localizations)
Shstr$ + ~" -e 'beep -- boop!!!'" ; comments allowed
Shstr$ + ~" -e 'return \"Error:\r\" & n & \"\r\" & e'" ; cancel button pressed "error"
Shstr$ + ~" -e 'end'" ; compiles to "end try"
asd

Debug "Finder Selection:"
processfilfol("myDebug", findersel(), 1, 1)

Shstr$ + "error!!!" ; triggers "script doesn't compile" sh error
qq()

Shstr$ + ~" -e 'try' -e 'error \"My Test Error\" number 1234'" + #myCatch ; catch all AS errors
qq()
Last edited by Piero on Mon Aug 21, 2023 1:15 pm, edited 2 times in total.
User avatar
Piero
Addict
Addict
Posts: 885
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Updated Post

Post by Piero »

Hope I didn't horrific things on (updated) post above!
I will use it also as template/reminder of what I can do with PB (e.g. use macros)… suggestions or criticism welcome!

PS:
https://developer.apple.com/library/arc ... -CH208-SW1

https://www.macosxautomation.com/applescript/sbrt/
https://www.macosxautomation.com/apples ... mples.html

https://yourscriptdoctor.com/applescript-date/

https://latenightsw.com/ (Script Debugger, good even when trial ends!)
https://latenightsw.com/freeware/
 
Post Reply