How to get the text output from the terminal? - applescript

I would like to know if it is possible to get the text output that is displayed in the terminal by running a shell script and display it in a Scrollable Text View, using applescript.
for example:
The output that the command: git clone https://github.com/torvalds/linux.git displays as shown in the image below would be displayed in a Scrollable Text view, would that be possible?
P.S:I'm sorry if the explanation was not clear, I hope someone understands and can help me!!

The steps for getting output from an asynchronous task like this are:
Create an
NSTask;
set its output to an NSPipe's fileHandleForReading;
register for a notification so you can get data to put into the textView as it becomes available.
To help with converting from Objective-C, Apple provided a conversion guide with their AppleScriptObjC Release Notes, but other than examples posted on various web sites and forums, that is about it. In general, for specific information about the various Cocoa classes and methods, you will need to look them up in Apple's documentation (for Swift you can switch to the Objective-C equivalent).
Note that an NSTextView does not have any terminal emulation (ANSI escape codes, etc), which is not trivial (take a look at iTerm2 for an example terminal application), so there won't be any cursor control. Git is also a little weird in that the progress uses standard error, so that will need to be redirected to standard output.
For a plain Xcode example, create a new AppleScriptObjC project and add the following statements to the AppDelegate:
property textView : missing value -- IBOutlet
property task : missing value -- this will be the NSTask
to startTask()
tell current application's NSTask's alloc's init() -- set up the task
its setCurrentDirectoryURL:(current application's NSURL's fileURLWithPath:(POSIX path of (path to desktop folder))) -- currentDirectoryPath deprecated in 10.13
set gitPath to "/Applications/Xcode.app/Contents/Developer/usr/bin/git"
its setExecutableURL:(current application's NSURL's fileURLWithPath:"/bin/zsh") -- launchPath deprecated in 10.13
its setArguments:{"-c", gitPath & " clone --progress https://github.com/torvalds/linux.git 2>&1"} -- combine stderr with stdout
its setStandardOutput:(current application's NSPipe's pipe())
its standardOutput's fileHandleForReading's readInBackgroundAndNotify()
set my task to it -- update script property
end tell
# set up notification observers
set notificationCenter to current application's NSNotificationCenter's defaultCenter
set readNotification to current application's NSFileHandleReadCompletionNotification
notificationCenter's addObserver:me selector:"dataAvailable:" |name|:readNotification object:(task's standardOutput's fileHandleForReading)
set terminateNotification to current application's NSTaskDidTerminateNotification
notificationCenter's addObserver:me selector:"taskTerminated:" |name|:terminateNotification object:task
set {theResult, theError} to task's launchAndReturnError:(reference) -- |launch| deprecated in 10.13
if theError is missing value then
log "Task Launched"
else
log "Error launching task: " & (theError's localizedDescription() as text)
end if
end startTask
on dataAvailable:notification -- get some output from the task
set theData to notification's userInfo()'s objectForKey:(current application's NSFileHandleNotificationDataItem)
if theData is not missing value and theData's |length|() > 0 then showResult(theData)
notification's object's readInBackgroundAndNotify() -- notify again when more data is available
end dataAvailable:
to showResult(resultData) -- append data to the end of the text view
set resultString to current application's NSString's alloc()'s initWithData:resultData encoding:(current application's NSUTF8StringEncoding)
set attributedString to current application's NSMutableAttributedString's alloc()'s initWithString:resultString
set theFont to (current application's NSFont's fontWithName:"Menlo Regular" |size|:12)
set theRange to (current application's NSMakeRange(0, attributedString's |length|()))
attributedString's addAttribute:(current application's NSFontAttributeName) value:theFont range:theRange -- use monospaced font
textView's textStorage()'s appendAttributedString:attributedString
textView's scrollToEndOfDocument:me -- 10.14+
end showResult
on taskTerminated:notification
current application's NSNotificationCenter's defaultCenter's removeObserver:me
repeat -- get any early termination leftovers
set theData to notification's object's standardOutput's fileHandleForReading's availableData
if theData is not missing value and theData's |length|() > 0 then
showResult(theData)
else
exit repeat
end if
end repeat
set my task to missing value -- clear script property
log "Task Terminated"
end taskTerminated:
In the Interface Editor, add a scrollable text view to the main window and connect it to the textView property, edit the currentDirectory and gitPath locations as needed, and put a statement in the applicationWillFinishLaunching hander to call startTask().
For something a bit simpler (shorter) to test with that still has a little output, the task arguments can be changed to something like:
its setExecutableURL:(current application's NSURL's fileURLWithPath:"/usr/sbin/system_profiler")
its setArguments:{"-detailLevel", "basic"} -- mini, basic, full
-- or --
its setExecutableURL:(current application's NSURL's fileURLWithPath:"/bin/zsh")
its setArguments:{"-c", "find /Users/$USER -iname '*.scpt'" } -- find scripts

Related

How can I copy result from Calculator.app to the clipboard using AppleScript

How do I copy the result of the Calculator.app including decimals.
By defaults is selected, so if you just do a CMD+C it copies it into your clipboard. I got this code from #jweaks
set the clipboard to {?????}
I tried entering many different options but I don't know what I should put.
How do I copy the result of the Calculator.app including decimals.
set the clipboard to {?????}
As you already know ⌘C can do it, however, if you want to use a set clipboard to method, then here is one way to go about it:
Example AppleScript code:
if not running of application "Calculator" then return
tell application "System Events" to ¬
set the clipboard to ¬
(get the value of ¬
static text 1 of ¬
group 1 of ¬
window 1 of ¬
process "Calculator")
Notes:
Does not require Calculator to be frontmost.
Does not require the use of keystroke or key code to accomplish the task.
Can set the value to a variable instead of the clipboard, if wanting to process it in a different manner.
The example AppleScript code, shown below, was tested in Script Editor under macOS Catalina and macOS Monterey with Language & Region settings in System Preferences set to English (US) — Primary and worked for me without issue1.
1 Assumes necessary and appropriate settings in System Preferences > Security & Privacy > Privacy have been set/addressed as needed.
In testing, the Replies pane in Script Editor returned, e.g.,:
tell application "System Events"
get value of static text 1 of group 1 of window 1 of process "Calculator"
--> "6200.549407114624506"
set the clipboard to "6200.549407114624506"
end tell
Then when pasting into a document it pasted as: 6200.549407114624506
Update to address comments
To address the ensuing comments by sebseb under my answer and specifically…
Is it possible to run the script every time I hit enter on Calculator? then copy the result.
Basic vanilla AppleScript is not that intelligent and does not have the ability in of and by itself to understand what one is doing in Calculator and know when one has pressed the enter key to then place the result on the clipboard.
One would have to use an intermediary, an application like Hammerspoon, where it can wait for the Calculator application being activated/deactivated or its window being focused/unfocused to then enabled/disable trapping the enter key being pressed on the keyboard to then run the script to perform an action to calculate the result by pressing the = key then copy the result to the clipboard.
This works because pressing the = key in Calculator is equivalent to pressing the enter key, thus enabling trapping the enter key to perform the necessary actions using AppleScript. It quite possibly can be done without using AppleScript and just Lua, the language used by Hammerspoon and its API. However, since I already use various AppleScript scripts in conjunction with Hammerspoon and can easily recycle some existing code I'll present an addendum to the original answer using both methods in Hammerspoon.
The following example Lua code and API of Hammerspoon is placed in the ~/.hammerspoon/init.lua file.:
-- Create a hotkey used to trap the enter key and disable it.
-- It will then be enabled/disabled as Calculator is focused/unfocused
-- When enabled and the enter key is pressed it runs the AppleScript script.
local applicationCalculatorEnterHotkey = hs.hotkey.bind({}, "return", function()
local asFile = "/.hammerspoon/Scripts/CalculatorResultToClipboard.applescript"
local ok, status = hs.osascript.applescriptFromFile(os.getenv("HOME") .. asFile)
if not ok then
msg = "An error occurred running the CalculatorResultToClipboard script."
hs.notify.new({title="Hammerspoon", informativeText=msg}):send()
end
end)
applicationCalculatorEnterHotkey:disable()
-- One of two methods of watching Calculator.
--
-- The other is below this one and commented out.
-- Initialize a Calculator window filter.
local CalculatorWindowFilter = hs.window.filter.new("Calculator")
-- Subscribe to when the Calculator window is focused/unfocused.
CalculatorWindowFilter:subscribe(hs.window.filter.windowFocused, function()
-- Enable hotkey when Calculator is focused.
applicationCalculatorEnterHotkey:enable()
end)
CalculatorWindowFilter:subscribe(hs.window.filter.windowUnfocused, function()
-- Disable hotkey when Calculator is unfocused.
applicationCalculatorEnterHotkey:disable()
end)
-- Alternate method to wait for Calculator and enable/disable the hotkey.
--
-- Uncomment below method and comment the above method to test between them. Adding the
-- multiple line opening '--[[' and closing '--]]' to above method and removed from below,
-- leaving 'local CalculatorWindowFilter = hs.window.filter.new("Calculator")' uncommented.
--[[
function applicationCalculatorWatcher(appName, eventType, appObject)
if (eventType == hs.application.watcher.activated) then
if (appName == "Calculator") then
-- Enable hotkey when Calculator is activated.
applicationCalculatorEnterHotkey:enable()
end
end
if (eventType == hs.application.watcher.deactivated) then
if (appName == "Calculator") then
-- Disable hotkey when Calculator is deactivated.
applicationCalculatorEnterHotkey:disable()
end
end
end
appCalculatorWatcher = hs.application.watcher.new(applicationCalculatorWatcher)
appCalculatorWatcher:start()
-- appCalculatorwWatcher:stop()
--]]
The following example AppleScript code is used in conjunction with Hammerspoon and is saved as CalculatorResultToClipboard.applescript in ~/.hammerspoon/Scripts/, and you'll need to create the hierarchical folder structure.
Example AppleScript code:
One can use either:
tell application "Calculator" to activate
tell application "System Events"
key code 24
delay 0.5
set the theResult to the value of static text 1 of group 1 of window 1 of process "Calculator"
end tell
set the clipboard to theResult
Or:
tell application "Calculator" to activate
tell application "System Events"
key code 24
delay 0.5
key code 8 using command down
end tell
To accomplish the task.
An alternate option, as previously mentioned, is to forgo the use of AppleScript and use the following example Lua code:
local applicationCalculatorEnterHotkey = hs.hotkey.bind({}, "return", function()
-- Press the '=' key to finish the calculation.
hs.eventtap.keyStroke({}, "=")
-- Copy the result to the clipboard.
hs.eventtap.keyStroke({"cmd"}, "C")
end)
applicationCalculatorEnterHotkey:disable()
This function would be used instead of the same function further above. It replaces the execution of the AppleScript script with keystrokes generated by Hammerspoon to accomplish the same tasks, while using the remaining example Lua code and the API of Hammerspoon already presented.
Notes:
With the example Lua code, as coded, the behavior of pressing the enter key is only trapped and modified to trigger the example AppleScript code, or if using the alternate option send Hammerspoon keystrokes, while Calculator has focus. The enter key should work normally in all other applications.
See my other Hammerspoon related answers for instructions to install it and utilize the information contained herein.
One in particle is:
A: How can I make preview stop wrapping around when paging?
If using Script Editor, the example AppleScript code is saved as Text in the File Format: pop-up menu in the Save dialog box.
The example Lua code and API of Hammerspoon and AppleScript code, shown directly above, were tested respectively with Hammerspoon and Script Editor under macOS Mojave and macOS Catalina with Language & Region settings in System Preferences set to English (US) — Primary and worked for me without issue1.
1 Assumes necessary and appropriate settings in System Preferences > Security & Privacy > Privacy have been set/addressed as needed.
Calculator.app doesn't have an AppleScript dictionary.
You have to script the UI with System Events
tell application "System Events"
tell process "Calculator"
set frontmost to true
keystroke "c" using command down
end tell
end tell

MacOS render images from PDFs (no third party render lib)

I'm trying to render 50k images using a Mac Mini. I don't know much about the system, but I need to use the built-in renderer like the Preview.app.
I currently tested to set up an Automator script that could take files selected in the Finder window, render images, and move them to another directory.
But running that script with 50k single-page PDFs does not work. Instead, it seems to use the memory to store the PDFs or something like that.
I have never written any AppleScript, Swift, or similar code, but I'm well versed in Bash and Java, so if I could do this from the command line, then it would be ideal, but if someone has a suggestion on an AppleScript that could solve this issue I'm all ears.
Thank you for reading this.
-- 1) Create --> **Quick Action** (that is, service)
-- 2) Set: **Workflow receives current PDFs in Finder.app**
-- 3) Add action **Run AppleScript** with following content.
-- 4) Save this service with name like "Render images from single_page PDFs".
use scripting additions
use framework "Foundation"
use framework "AppKit"
use framework "Quartz"
use framework "QuartzCore"
on run {input, parameters}
set destinationFolder to (choose folder with prompt "Please, choose Destination Folder")
set destFolderPosixPath to POSIX path of destinationFolder
repeat with aPDF in (get input)
(my rasterPDF:aPDF savingToFolder:destFolderPosixPath)
end repeat
return input
end run
on rasterPDF:aPDF savingToFolder:destFolderPosixPath
set aURL to (|NSURL|'s fileURLWithPath:(POSIX path of aPDF))
set fileName to aURL's lastPathComponent()
set baseName to fileName's stringByDeletingPathExtension()
set aPDFdoc to PDFDocument's alloc()'s initWithURL:aURL
set doc to (NSImage's alloc()'s initWithData:((aPDFdoc's pageAtIndex:0)'s dataRepresentation()))
if doc = missing value then error "Error in getting image from PDF"
set theData to doc's TIFFRepresentation()
set aNSBitmapImageRep to (NSBitmapImageRep's imageRepsWithData:theData)'s objectAtIndex:0
set outNSURL to |NSURL|'s fileURLWithPath:(destFolderPosixPath & baseName & ".png")
set outputPNGData to aNSBitmapImageRep's representationUsingType:NSPNGFileType |properties|:{NSImageCompressionFactor:0.8, NSImageProgressive:false}
outputPNGData's writeToURL:outNSURL atomically:true
end rasterPDF:savingToFolder:
NOTE: 1) the script renders image from first page of multiple_page PDFs as well. 2) with using repeat loop and with changing pageAtIndex property, someone can adapt my script to render images from all PDF pages.

AppleScript to remove icon from dock

I need to be able to run the script through Terminal only. I've seen other scripts that work as long as you change some settings in Accessibility; this is not an option for what I'm trying to do. I've tried the script below, but receive the following error:
0:13: script error: A real number can’t go after this identifier. (-2740)
tell application "System Events"
set dockPlistFile to property list file "~/Library/Preferences/com.apple.dock.plist"
tell dockPlistFile
tell property list item "persistent-apps"
set appTileItems to value of (every property list item whose value of property list item "tile-data"'s property list item "file-label" is not "Terminal")
set its value to appTileItems
end tell
end tell
end tell
tell application "Dock" to quit
I'm trying to get rid of the Terminal icon from the dock. How can I do this correctly?
I think this ask different answer will help you run a dock modification without changes to Accessibility settings.
Basically you'll chain a launch agent XML file to a shell script and call your apple script from within that.

Applescript to check App Store

Thanks for taking the time to read my question.
It's pretty simple, but i am a complete noobie to this, so am having some trouble.
Is it possible to have an applescript that will check the mac app store for updates, and if there are, output the number of updates to someplace?
A good example of this is (if you are aware of it) the geeklets that check for unread mail, and then outputs it to the desktop.
EDIT:
I downloaded a geeklet for the unread mail (as referenced above), and using that as a starting point, I tried to write my own script.
set run_bool to 1
tell application "System Events"
set run_bool to count (every process whose name is "App Store")
end tell
if run_bool is 1 then
tell application "App Store"
set update_count to 0
set output_string to ""
repeat with upd in Apps in Updates
if upd's download is not true then
set update_count to update_count + 1
end if
end repeat
if update_count is 0 then
set output_string to "zero"
else if update_count is 1 then
set output_string to "one"
else
set output_string to "two"
end if
end tell
else
set output_string to "not running"
end if
return output_string
now this is not my final code, but simply to check to see if it will work and what the output would be.
On compilation I get an error saying
error "The variable Updates is not defined." number -2753 from "Updates"
as well as
Syntax Error
Expected end of line but found unknown token
Also, when I stopped compilation, this appeared below the last line in my code
tell application "GeekTool Helper"
activate
«event ascrgsdf»
Any help is appreciated.
#foo is pretty right on with his idea. This code only requires one line. In the second line, I used display notification, but you substitute it with you preferred method to pass on the value.
tell application "System Events" to tell (first application process whose ¬
frontmost is true) to set returnValue to title of ((first menu item whose title ¬
begins with "App Store") of menu "Apple" of menu bar 1)
display notification returnValue
Result:
"App Store…, 1 update"
menu bar items are accessible everywhere (e.g. windowed/numeral desktop mode, fullscreen mode, dock hidden/disabled).
Make sure accessibility is enabled for Script Editor, or whichever app you use to invoke the script, to gain access to the UI.
There is just one weird thing: if I had used begins with "App Store..." instead of begins with "App Store", the code would be a dud. I don't know why - it might has to do with escaped characters and the .... Anyone who knows please enlighten me with a comment.
As for your code, I can tell from AppleScript Dictionary that Updates is not a property of App Store.app. Nor is any other categories in the UI. To get to the Dictionary, open Script Editor and press CMD+SHIFT+O
In addition, if you want to use return statement, you need an explicit handler. In other words, you need to wrap the code between on run and end run.

Smooth UI changes in cocoa-applescript (Xcode)

I'm trying to write a simple Xcode cocoa-applescript program that executes a bash script on each subfolder of a folder and shows a progressbar while doing it.
I have a NSProgressIndicator set up to be linked to barStatus and well a NSTextField to be linked to lblStatus to show some informative text:
property barStatus : missing value
property lblStatus : missing value
this is the loop where the main action takes place:
tell application "Finder" to set subfolders to every folder of folder macPath
barStatus's setIndeterminate_(false)
barStatus's setDoubleValue_(0)
barStatus's setMaxValue_(count of subfolders)
repeat with eachFolder in subfolders
set posixPath to (POSIX path of (eachFolder as text)) as text
-- set text of progress indicator text
lblStatus's setStringValue_("Resizing '" & ( do shell script "basename \"" & (posixPath) & "\" | sed \"s/^.*_//\"" ) & "'...")
delay 0.5
-- do the shell script
do shell script "bash ~/.uploader/image.sh \"" & posixPath & "\""
-- step the indicator
barStatus's incrementBy_(1)
end repeat
lblStatus's setStringValue_("Done!")
All seems to work properly, yet the UI is somewhat glitchy. Instead of just increasing smoothly, the progressbar disappears on each step and gets shown for a short while, then dissappears again. The text in lblStatus does get changed smoothly.
Things get totally lost when I remove the delay from the loop: no UI changes are made (even though the scripts get run properly) until the loop is finished. So the progressbar just dissappears and reappears filled out when the loop is done.
Here's a youtube video of the flickery UI.
What am I doing wrong? How can I have xcode draw the progressbar smoothly?
Note that this is the first time I write an Xcode app, and that my knowledge of Applescript is somewhat sketchy.
EDIT:
I found that calling the function that processes the same function does not have a flickery UI when called form a menu item (or with a key combo bound to that menu item).
I'm not sure why the progress bar hides itself. Did you bind the visible property of it to something that may make it hide?
Things get totally lost when I remove the delay from the loop: no UI
changes are made
In general though when "no UI changes are made" it's because the application is too busy on the main thread to update the interface items in real time. All of your code is running on the main thread. You need to make some of it happen on a background thread. As such I have 2 suggestions for you to try.
First try using the progress bar's method "setUsesThreadedAnimation:". Add this just above your repeat loop...
barStatus's setUsesThreadedAnimation_(true)
Second, if that doesn't help then try moving your repeat loop work onto a background thread (using NSThread's detachNewThreadSelector:)... but make the interface updates happen on the main thread. I don't know ApplescriptObjC language so the following code is probably completely wrong. You'll have to write it properly but it will show you the idea...
[NSThread detachNewThreadSelector:#selector(processFolderListOnBacgroundThread_) toTarget:self withObject:subfolders];
-- this will happen on the main thread
on updateProgressBarByOne_()
barStatus's' incrementBy_(1)
end updateProgressBarByOne_
-- this will happen on the main thread
on updateStatusTextWithText_(statusText)
lblStatus's' setStringValue_(statusText)
end updateInterfaceItemsOnMainThreadWithObject_
-- this will happen on a background thread
on processFolderListOnBacgroundThread_(subFolders)
repeat with eachFolder in subfolders
set posixPath to (POSIX path of (eachFolder as text)) as text
-- set text of progress indicator text
my updateStatusTextWithText_("Resizing '" & ( do shell script "basename \"" & (posixPath) & "\" | sed \"s/^.*_//\"" ) & "'...")
-- do the shell script
do shell script "bash ~/.uploader/image.sh \"" & posixPath & "\""
-- step the indicator
my updateProgressBarByOne_()
end repeat
my updateStatusTextWithText_("Done!")
end processFolderListOnBacgroundThread_
If you use the background thread approach you'll probably have to make the buttons in your interface inactive while the background thread is working (so the user can't press them and start a new task). So just set their enabled property to false prior to calling "detachNewThreadSelector:" and enable them again after the work is finished. You can do this by having another handler which enables them and call that from the end of the background thread code.

Resources