Making a compile Script for BareBones Editor with Results browser.

Hello

Just for the record.
Another title would have been: Enter the Dragon! Access Clang from BBEdit or TextWrangler.

This script shows how to script a results browser of TextWrangler or BBEdit, by the simples means possible.

The script in itself is useful, if you’d like to compile Foundation examples, and see how they run from outside Xcode, as you may have a project there (or several), that you are working on.

So, if there are errors in the .m file, then a Results Browser window is thrown up as the front window of your BareBones Editor of choice. Otherwise, you are presented with the foreground Terminal window, (one is created if none exists), and your compiled example code is executed there.

Here is the script, you should really call it Compile Obj-C
After you have installed it, and assigned cmd-R as the short cut key, then use the file below to verify that it works.

Edit
And when you have affirmed that you are getting up the results browser, then please fix some of the errors, and see that you indeed get the executable running in terminal.


-- Copyright © 2014 McUsr All Rights Reserved.
# This script compiles an Objective-C file from BBEdit or TextWrangler
# The Objective-C file is intended to use the Foundation framework only.
# if you want to compile other stuff, then you'll have to configure the
# command line


tell application "BBEdit"
    save text document 1
    set sourceFile to file of text document 1
end tell

tell (a reference to text item delimiters)
    set {tids, contents of it} to {contents of it, ":"}
    set pieces to text items of (sourceFile as string)
    set sourceFileName to the last text item of pieces
    set contents of it to "/"
    set sourceFileFolder to "/" & text items 2 thru -2 of pieces as text
    set contents of it to tids
end tell
# we compile the file here, we redirect any standard error, and we amend the 
# exit code of the comple-commandto the output, so that we understand what 
# path to take 
set compileScript to "cd " & (quoted form of sourceFileFolder) & "; clang -fobjc-arc -framework Foundation  -Weverything  " & quoted form of sourceFileName & " -o a.out 2>&1; echo $?"
set compilerOutput to paragraphs of (do shell script compileScript)

try
    set exitCode to item -1 of compilerOutput as number
on error
    display alert "Something is wrong with the compile script Aborting."
    error number -128
end try

if exitCode = 0 then
    # Time to run the command in a terminal window. to see some results :)    
    tell application "Terminal"
        activate
        set shell_script to "cd " & (quoted form of sourceFileFolder) & " ; a.out "
        if (count windows) is 0 then
            do script shell_script
        else
            do script shell_script in the front window
        end if
    end tell
else
    set locationPars to {}
    # we fire up a results browser in the app, so that we can see the errors.
    repeat with anErrorLine in compilerOutput
        set locationText to do shell script "sed -n '/.*[:][1-9]*[:][1-9]*.*[:].*/p' <<<" & quoted form of anErrorLine
        if locationText is not "" then set end of locationPars to locationText
    end repeat
    
    set locationData to {}
    repeat with aLocPar in locationPars
        tell (a reference to text item delimiters)
            set {tids, contents of it} to {contents of it, ":"}
            set pieces to text items of aLocPar
            set err_msg to the last text item of pieces
            set lineNmbr to text item 2 of pieces
            set theFname to text item 1 of pieces
            set contents of it to tids
        end tell
        copy {err_msg, theFname, lineNmbr} to end of locationData
    end repeat
    
    tell application "BBEdit"
        set resultItems to {}
        repeat with location in locationData
            
            set {locationMessage, locationFile, locationLine} to location
            
            set hits to (text documents whose on disk is true and URL contains locationFile)
            if (count of hits) > 0 then
                set mydoc to first item of hits
                set locationLine to locationLine as number
                set myline to line locationLine of mydoc
                set s_offset to characterOffset of myline
                if (count of characters of myline) > 0 then
                    set e_offset to characterOffset of last character of myline
                else
                    set e_offset to s_offset
                end if
                set resultEntry to {start_offset:(s_offset - 1), end_offset:e_offset, message:locationMessage, result_kind:error_kind, result_file:file of mydoc, result_line:locationLine}
                copy resultEntry to end of resultItems
            end if
            
        end repeat
        
        make new results browser with data resultItems with properties {name:"Compile Errors in " & sourceFileName}
    end tell
end if

#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"Hello World"); NSInteger myint = "Stunned" } return 0; }
Enjoy.

Thanks for posting this! I have a similar script for compiling C++ files, but I am sending compilers’s errors to a new TextWrangler document, which I don’t like much. I hadn’t thought of using a results browser: that’s really cool! I plan to take a closer look at your code and I hope to be able to use it with C++ (where error messages can be of biblical length, especially when using templates).

I suggest two improvements to your script (OS X 10.9 only):

  1. display a notification when compilation is successful, along these lines (untested code):

-- [...]
if compilerOutput is not equal to "" then
    set notificationText to "There are some warnings."
else
    set notificationText to "No issues."
end if
display notification notificationText with title "Build succeeded" subtitle sourceFileName

  1. add a progress bar using a script library (which you can put inside your script bundle), so that there is some feedback when the code is compiling. You may use Shane Stanley’s excellent example code.

Hello.

Thank you for the feedback.

The reason that I code the extraction of comments that way is to cut off the line below, showing exactly where there is something wrong by pointing ^^^^^ below it. It is an approach that works well with Clang, or so it seems.

I made this for small snippets, or “single-file” stuff, so I’ll be there when I see either the executable running, or get The Results Browser in my face. I don’t see a need for notification.

And for larger C-Based project, I use(d) Vim and make files. I think you can use this with makefiles as well, tailor it too your needs as you see fit. -And you can use the Results Browser for all kinds of parsing, given that you get some line numbers and error messages. This is really the reason (together with BBEdi’ts easy to create and use templates) that I implemented the environment in one of those. The Results Browser is a really cool tool, once you get your head around it.

But my preferred Development Environment is of course Xcode, I haven’t words for how good it is really. :slight_smile:

Edit

I actually have some words for what I feel about Xcode, and that is that it is as hot-dirty-sexy as Cocoa and Objective-C. :slight_smile: -But you have to invest some time in it.

Hello.

I see the need for output while something is compiling if you are going to compile more than something that takes merely milliseconds, and actually, even then it would be nice with some feedback.

I figure the best way to do this, is to create a temporary fil in the script, and the executing the Clang command line, in the terminal window, so you get any stats and so on, and see that stuff is actually happening, teeing the output to a temporary file as well. This scheme lets you also keep the previous output in the terminal window. -This feature is not so important for me, but I will at some time at my own leisure upgrade it to show this output, and also to assure that the front terminal window is indeed dedicated to the same directory as where the files are compiled.

I have just assured that the tee command will suffice and that it is no need for mkpipe. :slight_smile:

I believe Clang is the English name of the dragon that Bilbo Baggins met in “The Hobbit”

This version Reflects the output of clang from the Terminal window into the
Results Browser of BBEdit or TextWrangler (see above). This as a meaningful way of showing progres during longer compilers. Thanks to druido for commenting on that.

This version also differs between errors and warnings in the compiler window.

Some speed improvements.

Edit

I had to add some parenthesis around the do shell script line that “cats back” the output for some wicked reason.

-- http://macscripter.net/viewtopic.php?pid=171134#p171134
-- ( I have -Werror enabled).
# This script compiles an Objective-C file from BBEdit or TextWrangler
# The Objective-C file is intended to use the Foundation framework only.
# if you want to compile other stuff, then you'll have to configure the
# command line
(*
20.02.14T22:52:04

New model, "reflects" the output from the terminal window into
the results browser, as a meaningful way of showing progress.
-> if error from compile, otherwise executes a.out

21.02.14T00:27:51

Added some more robustness, when parsing the result code of the compile.
*)
main()
on main()
	tell application "BBEdit"
		close every results browser
		save text document 1
		set sourceFile to file of text document 1
		try
			tell me to do shell script "rm ~/Library/Caches/net.mcusr.Clang-result.tmp "
		end try
	end tell
	
	tell (a reference to text item delimiters)
		set {tids, contents of it} to {contents of it, ":"}
		set pieces to text items of (sourceFile as string)
		set sourceFileName to the last text item of pieces
		set contents of it to "/"
		set sourceFileFolder to "/" & text items 2 thru -2 of pieces as text
		set contents of it to tids
	end tell
	# we compile the file here, we redirect any standard error, and we amend the 
	# exit code of the comple-commandto the output, so that we understand what 
	# path to take 
	set compileScript to "cd " & (quoted form of sourceFileFolder) & "; (  clang -fobjc-arc -framework Foundation  -Weverything " & quoted form of sourceFileName & " -o a.out 2>&1 ; echo $? )| /usr/bin/tee ~/Library/Caches/net.mcusr.Clang-result.tmp  "
	# We tee the file, an preserve the error code, so that we show the output in the front terminal window, (and can see progress)
	tell application "Terminal"
		activate
		if (count windows) is 0 then
			do script compileScript
		else
			do script compileScript in the front window
		end if
	end tell
	delay 1
	set compileroutput to (do shell script "/bin/cat ~/Library/Caches/net.mcusr.Clang-result.tmp" without altering line endings)
	try
		set exitCode to paragraph -2 of compileroutput as number
		
	on error
		try
			set exitCode to paragraph -1 of compileroutput as number
		on error
			display alert "Something is wrong with the compile script Aborting."
			error number -128
		end try
	end try
	if exitCode = 0 then
		# Time to run the command in a terminal window. to see some results :)	
		tell application "Terminal"
			activate
			set shell_script to "cd " & (quoted form of sourceFileFolder) & " ; a.out "
			if (count windows) is 0 then
				do script shell_script
			else
				do script shell_script in the front window
			end if
		end tell
	else
		set locationPars to paragraphs 1 thru -2 of (do shell script "sed -n '/.*[:][1-9]*[:][1-9]*.*[:].*/p' <<<" & quoted form of compileroutput without altering line endings)
		script f
			property locationData : {}
		end script
		
		repeat with aLocPar in locationPars
			tell (a reference to text item delimiters)
				set {tids, contents of it} to {contents of it, ":"}
				set pieces to text items of aLocPar
				set err_msg to the last text item of pieces
				set errorType to words of item 4 of pieces
				set lineNmbr to text item 2 of pieces
				set theFname to text item 1 of pieces
				set contents of it to tids
			end tell
			copy {theFname, lineNmbr, errorType, err_msg} to end of f's locationData
		end repeat
		log f's locationData
		tell application "BBEdit"
			set resultItems to {}
			repeat with i from 1 to (count f's locationData)
				
				set {locationFile, locationLine, errorType, locationMessage} to item i of f's locationData
				
				set hits to (text documents whose on disk is true and URL contains locationFile)
				if (count of hits) > 0 then
					set mydoc to first item of hits
					set locationLine to locationLine as number
					set myline to line locationLine of mydoc
					set s_offset to characterOffset of myline
					if (count of characters of myline) > 0 then
						set e_offset to characterOffset of last character of myline
					else
						set e_offset to s_offset
					end if
					if errorType contains "warning" then
						set eType to warning_kind
					else
						set eType to error_kind
					end if
					set resultEntry to {start_offset:(s_offset - 1), end_offset:e_offset, message:locationMessage, result_kind:eType, result_file:file of mydoc, result_line:locationLine}
					copy resultEntry to end of resultItems
				end if
				
			end repeat
			
			make new results browser with data resultItems with properties {name:"Compile Errors in " & sourceFileName}
		end tell
	end if
end main


Hello.

I just added some parenthesis around the do shell script line I use to “cat back” the error output from the compiler with.
I am not sure why I suddenly had to do that, but the fix works.

Hello.

I have harnessed the script slightly.

Hello.

I have hopefully removed the last bug, which was that I read an empty paragraph out of the sed filter, which caused the last compiler Error to be copied twice into the Result Browser.