The Shell Class


The Shell class can be run in three modes: synchronous, asynchronous, and interactive. The mode is set using the Shell.Mode property, which is described next. When using synchronous mode, you use the Shell class to execute a command and then get the results back immediately. This is a blocking method, which means that nothing else happens in the sequence of code until the results are returned. Using asynchronous mode, you can execute a command, but you do not need to wait for the results. Instead, you wait for the DataAvailable event to be triggered. Interactive mode is similar to asynchronous mode except that instead of executing only one command, you can execute several commands in sequence, based on the results from the previous command.

Although synchronous mode is a simple way to run the Shell, I think running it asynchronously or interactively provides a lot more flexibility. Because the two nonsynchronous modes rely on events, the Shell is often used like a control and can be dragged to a Window and programmed there. The sample application treats the Shell like a control.

Shell Properties

Whether the Shell is synchronous, asynchronous, or interactive is determined by the Mode property:

Shell.Mode as Integer 


The values you can use are

Synchronous

0

Asynchronous

1

Interactive

2


These values can be set at runtime.

Synchronous Execution

The following properties are used when running the Shell in synchronous mode. Prior to REALbasic 2005, Windows had to use synchronous mode, but it doesn't any longer, so you will probably not need to use these:

Shell.TimeOut as Integer


TimeOut is measured in milliseconds and is used only when running synchronously on Windows. Use -1 to wait indefinitely for the results.

Shell.ErrorCode as Integer


The ErrorCode is supplied by the system. It's a zero if all is well, and another number if there is a problem. When you execute a command in synchronous mode, you should check the result and if it is zero, you can retrieve the standard output of the command using the following property:

Shell.Result as String


This is used only in synchronous mode, and it contains the entire output. In the other modes, you will use the ReadAll method to access the latest data.

Asynchronous and Interactive Execution

The following two properties are used when using modes other than synchronous. The first checks to see whether the Shell is still running, and the second is the process ID number of an interactive Shell process:

Shell.IsRunning as Boolean Shell.PID as Integer 


Shell Events

The following events are triggered only when using asynchronous or interactive modes. The following event is triggered when new data is available in the buffer:

Shell.DataAvailable Handles Event


When the DataAvailable event is triggered, you can retrieve the most recent data from the buffer by calling the ReadAll method. The following event lets you know that all the data has been sent and the process that you initiated with the execution of the command has terminated:

Shell.Completed Handles Event


Shell Methods

You execute an application by calling the Shell.Execute method:

Shell.Execute(Command as String, [Parameters as String])


There are two ways to pass arguments to the method. The Command parameter represents the path to the actual application or program that is to be executed (unless the appropriate PATH environment variable has been set, and then you can just use the name of the application). The optional Parameters parameter can be used with additional arguments that get passed to the program. You can include the arguments in the Command parameter, but the REALbasic documentation suggests that you use the Parameters parameter if there are spaces in the path to the application being executed.

For example, if there is a Shell instance, Shell1, you can execute the "cd" command used earlier, like this:

Shell1.Execute("cd /usr/local/bin") 


Or you can use it like this:

Shell1.Execute("cd", "/usr/local/bin") 


When using asynchronous or interactive mode, you can call the poll method, which may trigger a DataAvailable event. Generally speaking, I don't think it's necessary to use. No value is returned; the only response is the possible triggering of the DataAvailable event.

Shell.Poll


The following method accesses the data buffer. When called, it returns all the data in the buffer and then clears the buffer. This is used in asynchronous and interactive modes only.

Shell.ReadAll as String


There are two methods used to send additional text to the command line. It is used when you are in interactive mode and your class needs to send additional data to the command-line application based on a request from the application. The WriteLine method appends a linefeed to the string, which is the equivalent of pressing the Enter key after typing your command into the shell.

Shell.Write(Text as String) Shell.WriteLine(Text as String) 


The following method closes the shell:

Shell.Close


Subclassing the Shell

In the coming examples, I will use three subclasses of the Shell to do a few different things. The first is mwShell, which is a subclass of Shell that adds some convenience functions. The second is AntShell, which is a subclass of mwShell and coordinates the work of reading Ant build files, which I will discuss momentarily. I've also included a third class, SVNShell, another subclass of mwShell, which is unrelated to Ant, but demonstrates some more advanced issues with respect to the using the Shell class effectively. SVNShell is a wrapper for command-line access to Subversion, which is a very good and free version control system that I use. All these examples are combined into the Ant Shell application that you can download.

There are two methods in particular to pay attention to: addOpt and addArg. These two methods provide a more flexible interface to handling the arguments that get passed to the command line. It accommodates two kinds of arguments. The first type is the kind I used in the cd example, an argument that is a single string of text, such as:

cd /usr/local/bin


The second type of argument is called an option, which is made up of two parts, the option name (or key) and the value for the option. The following example using ant indicates how a particular build file is specified using the command line:

ant -f /path/to/build.xml 


When you add an option using mwShell, you pass both the option and the option value as arguments. This can be helpful when building long commands.

mwShell Inherits Shell

mwShell is a Shell subclass that contains some convenience methods related to building commands, setting certain environment variables, and displaying the output of executed commands.

You should also pay special attention to how the results that come from executing something on the Shell are displayed in the EditField. Because the Shell is asynchronous, you do not have all the results back in one long String. You get back bits and pieces. This example shows you how to effectively append new text to an EditField so that it flows naturally and doesn't cause the screen to flicker. You can see how this is done in the mwShell.print method.

The following properties are used by mwShell. The first two are used to build the command that will be executed, and the last two work with displaying the output of the command. The buffer stores all the data retrieved, and the view property is set to the EditField that will be displaying the data.

Property cmd as String Property args(-1) as String Property view as EditField Property buffer(-1) as String 


Next you will see the list of methods implemented by mwShell. Be sure to read the comments in each method to see what is happening and why it is being done that way.

Listing 7.1. Sub mwShell.Constructor(aPath as String, aPathType as integer)

// Since the PATH environment variable may not be set // you can set it in the Constructor. You do not necessarily // need to set this if you know the complete path to the // application. setPath(aPath, aPathType) 

Listing 7.2. Sub mwShell.addArg(myArg as String)

// Append an argument to the arguments // array of mwShell. args.Append myArg 

Listing 7.3. Sub mwShell.addOpt(option as String, arg as String)

// Add an option to the arguments array. // Quote the arg if it includes spaces. If instr(arg,   " ") <> 0 Then           arg =  "'" + arg +  "'" End If args.append(option + " " + arg) 

Listing 7.4. Sub mwShell.setCmd(command as String)

// Set the value for the command to be executed Me.cmd = command 

Listing 7.5. Sub mwShell.exec()

// This method takes the command in mwShell.cmd // as well as the arguments in the args array // to build a complete command to be executed. Dim full_cmd as String // build complete command, including arguments If Ubound(args) > -1 Then        full_cmd = me.cmd + " " + join(args, " ") Else        full_cmd = me.cmd End If // Test to see if the shell is running. If it is // running, then execute the command using WriteLine. If me.IsRunning Then         reDim buffer(-1)         me.WriteLine full_cmd Else // If the shell is not already running, then call execute.         reDim buffer(-1)         me.execute full_cmd End If 

Listing 7.6. Sub mwShell.print(s as String)

// The view property is an EditField where the // output of the application is displayed. If // the property is not Nil, then the output is // appended to the text of the EditField using // the following technique, which avoids making // the screen flicker. The selStart value is // set to the length of the text field, and the // selLength value is set to zero. The new data // is then assigned to the selected text. If view <> Nil Then        view.selStart = LenB(view.Text)        view.selLength = 0        view.selText = s End If 

Listing 7.7. Sub mwShell.setPath(path as String, setPathType as Integer)

// Set the PATH environment variable. // The setPathType Integer indicates whether the // path should be appended,inserted at the beginning // or if it should replace the existing path. // You can use the class constants: kAppendPath, // kInsertPath, kNewPath Dim oldPath, NewPath as String Dim sep as String // The separator used in the PATH environment variable // varies between Windows and the Unix-like systems. Set // the appropriate value for the sep variable here. #If targetWin32 Then         sep = ";" #else         sep = ":" #end if oldPath = System.EnvironmentVariable("PATH") // Build the new path as requested Select Case setPathType Case kAppendPath          NewPath = oldPath + sep + path Case kInsertPath          NewPath = path + sep + oldPath Case kNewPath          NewPath = path Else          NewPath = oldPath + sep + path End Select // Set the environment variable System.EnvironmentVariable("PATH") = NewPath 

Listing 7.8. Sub mwShell.setMode(aMode as Integer)

// When using the Shell as a control, you can // set the Mode using the IDE. In other cases, // you need to set it programmatically, but // it cannot be set in the constructor, so // this method is used. Me.mode = aMode If me.mode = 2 Then         execute "sh" End If 

Listing 7.9. mwShell.Sub setView(anEditField as EditField)

// Set the EditField used to view the output. Me.view = anEditField 

Listing 7.10. Sub mwShell.setWorkingDirectory(aPath as String)

// This sets the working directory, which is // the directory used as the frame of reference for // calculating relative paths. // // The "cd"   command is basically the same for both // Unix-like systems and Windows and changes the // directory to the path that is passed to this method. If IsRunning Then         writeLine("cd" + aPath) Else        // ignore End If 

Listing 7.11. Sub mwShell.reset()

// Reset all the values to get ready to execute another command. Me.cmd = "" reDim args(-1) reDim buffer(-1) 

Now you have a base class that you can build on. In the next section I will subclass mwShell and create a class to be used with Subversion version-control software.

Subversion

As I mentioned earlier, Subversion is a version control system and is usually used by programmers to keep track of versions of their software. If you make an addition to your program and it suddenly stops working, Subversion will let you access the previous working version of your code and isolate the change that caused the problem.

Like many version control systems, Subversion has facilities for storing all the files on a centralized server using one of a handful of methods, like WebDav and SSH. There are two parts to Subversionthe server and the client. The client checks out files from the server, makes changes to them locally, and then commits those changes back to the server. Whenever you check out files from the server or commit changed files, you need to sign in.

I have included a class that is designed to make working with Subversion through the Shell easier. Some of the more common commands have been implemented as methods of the SVNShell subclass, which is one way to make dealing with the Shell feel more like dealing with a regular class, making it much easier to use. The key feature I want to share with you, however, is how this class waits for a response from the server requesting a username and password and how it responds to it. This is the primary benefit of what the Shell class calls interactive mode. You are able to send commands to the Shell, read the results, and then respond with more commands, which is a very powerful feature.

All the Shell subclasses are packaged up in the Ant Shell application, even though they are really distinct activities. The main window, Window1, allows you to interact with the SVN shell and enter raw commands to be executed on the command line. You can also click a Target button to see the graphical interface to using Ant. From there you will be able to select targets and execute them.

Note that you will need to have Subversion installed on your computer separately for the Subversion-specific commands to work. If you do not have Subversion installed, you can still use the application to access the shell and execute commands. You will also need to make sure the PATH environment variable includes the path to the Subversion installation.

Figure 7.3 shows this application in action. When the application first launched, I clicked on the Init Subversion Client button, which initialized the SVNClient object. I then typed in the Windows command time in EditField1 (the topmost EditField in the example), and the result was the following:

Figure 7.3. The Shell Window example.


The current time is: 9:02:34.83 Enter the new time: 


Because the SVNClient1 object is operating in interactive mode, I am able to respond to the prompt to enter the new time. Again in EditField1, I typed the new time I wanted to set my computer to:

9:01:00.00


In Figure 7.3, you can see the results of these steps still displayed. (The time on my computer did get changed as a result.)

SVNclient Inherits mwShell

In the previous section, I showed you how the application looks and gave you an example of how I used the application to change the time on my computer. The reason this is included in the sample application is so that you can see an example of how to use the interactive features of the Shell. In addition to entering multiple commands like I just showed you, this subclass will also automatically respond to certain prompts. When you execute an SVN command, the shell will prompt you for a username and password and the SVNClient object will look for this prompt and respond to it when it appears.

The properties of the class store the username and password that will be used to access the Subversion repository:

Property user as String Property password as String 


This example also shows how you can extend the Shell class to give it a more natural, object-oriented interface. Several Subversion command-line arguments are implemented as methods that your program can call. For example, to view the log of the current version-controlled directory, you can do the following:

SVNclient1.log 


The following events are used in asynchronous or interactive mode. The DataAvailable event is the important one because whenever it is triggered, it checks to see whether a password is being requested, and if it is, it sends the username and password back to the shell.

Listing 7.12. Sub SVNclient.Completed() Handles Event

// The request is completed MsgBox "Completed!" 

Listing 7.13. Sub SVNclient.DataAvailable() Handles Event

// Track the data that is coming from the shell // as the request is executed. When the shell // returns the password prompt, send the // username and password to the shell. Dim s as String Dim subs as String s = Me.ReadAll print s subs = Trim(Right(s, 10)) If subs = "password:" Then     // The shell is requesting a password     me.writeLine(me.password)     print "Sending password"     reDim buffer(-1) Else     // It's not a password prompt,     // keep looking for return values     buffer.append(s) End If 

Listing 7.14. Sub SVNclient.status(source_String as String)

// Predefined SVN command // Get the status of the current directory Me.cmd = "svn status" + source_String Me.exec() 

Listing 7.15. Sub SVNclient.add(source_path as String)

// Predefined SVN command // Add a file to the version control repository Me.cmd = "svn add" + source_path Me.exec() 

Listing 7.16. Sub SVNclient.delete(source_path as String)

// Predefined SVN command // Delete a file from the version control repository Me.cmd = "svn del" + source_path Me.exec() 

Listing 7.17. Sub SVNclient.update(source_String as String)

// Predefined SVN command // Update the local copy with updated files from // the repository server. Me.cmd = "svn update source_String" Me.exec 

Listing 7.18. Sub SVNclient.commit(source_String as String, msg as String)

// Predefined SVN command // Commit local changes to the server repository. Me.cmd = "svn commit" + source_String +  " -m " + msg 

Listing 7.19. Sub SVNclient.log()

// Predefined SVN command // View the log. Me.cmd = "svn log" Me.exec() 

Listing 7.20. Sub SVNclient.setUser(aUser as String, aPassword as String)

// Configure username and password Me.user = aUser Me.password = aPassword 

Listing 7.21. Window1 Inherits Window

// The primary application window. Property ant as AntShell 

Listing 7.22. Sub Window1.PushButtonSVN.Action() Handles Event

// Initialize the SVNClient instance // The view is the EditField where the results of executing // any command will be displayed SVNclient1.setView(window1.EditField2) // This sets the Path environmental variable // Note that this path is appropriate for Linux/Mac only #if Not (TargetWin32) Then     SVNclient1.setPath("/usr/local/bin", SVNclient1.kAppendPath) #endif // The mode is interactive, which means that I can send // multiple requests in sequence and respond to the results // of each request. In this example, Subversion requires a // username and password, so the application will have to wait // until Subversion requests it, and then pass the information // to it, all of which requires the application to be in // interactive mode. New in REALbasic 2005, Windows now supports // interactive mode, too. You can use asynchronous mode if you // only want to execute a single command. The results are // returned in an event. // // Synchronous mode is blocking, which means you want to execute // a command and have your application wait until the results are // returned. SVNclient1.setMode(2) // Identify the directory that SVN will use as the root directory // Relative links will be calculated from this directory. // The Windows Shell class does not seem to acknowledge a working // directory, so all relative paths are calculated relative to // the home directory of the shell application. #if Not (TargetWin32) then     SVNclient1.setWorkingDirectory("/Library/WebServer/Documents/Metawrite") #endif // Set the username and password for Subversion SVNclient1.setUser("subvn", "password") // Clear the buffer reDim SVNclient1.buffer(-1) 

Listing 7.23. Sub Window1.PushButtonExec.Action() Handles Event

// Execute a generic command using the text in // EditField1. It uses the SVNClient instance, but // will execute any command you pass, regardless of // whether it is a valid SVN command or not. // However, the SVNClient should be initialized first // by pushing the InitSubversion Client button. Dim s as String s = EditField1.Text // Set the text from EditField1 to the command svnclient1.setCmd(s) // Execute the command svnclient1.exec // Clear the command-line EditField EditField1.Text = "" 

The SVNClient class is a good example of how to subclass the Shell class in such a way that you can interact with the Shell just like you are working with any other class. In the next section, I go a step further and integrate the Shell more closely with the user interface.

Ant Shell

Ant is a product of the Apache Foundation; it is a Java application that was developed as a replacement for Make, which is used to managed the build and compile process for applications. Ant is written in Java and is designed to work especially well with building Java applications, but it has also proven to be a very flexible tool for doing all sorts of things. Although it is not a full-blown scripting language, there are a lot of things it can do very well that you would normally do with a scripting languagesuch as copying and deleting groups of files, transferring files by FTP to a server, and so on.

The Ant application is generally run from the command line, where it processes special XML files that contain the instructions it needs to do whatever it is that needs to be done. These XML files are usually named build.xml, but they do not have to be. Each build file represents a project, and each project is given a number of targets. Targets can be dependent on other targets, which means that the task will not run until the target that it is dependent on has run. Targets perform tasksspecific things that you need to have done. The build.xml file specifies all this. Here is an example Ant build.xml file:

Listing 7.24. An Ant build.xml file

<project name="ExampleProject" default="init" basedir=".">     <description>          This is an example of a basic build.xml file     </description>   <!--Properties to be used later in the file -->   <property name="src" location="src"/>   <property name="build" location="build"/>   <target name="init">     <!-- Create the time stamp and output it to the console -->     <tstamp/>    <!-- Create the directory referred to in the build property-->     <mkdir dir="${build}"/>   </target>   <target name="clean"          description="Delete the build directory" >     <delete dir="${build}"/>   </target> </project> 

This project is called "ExampleProject" and the default target that will be executed if no other target is specified is the "init" target. The basedir identifies the directory from which all relative paths should be calculated. The use of "." as the value means that the directory you were in when you executed Ant will be the base directory. Finally, the project has a description.

There are two targets, init and clean. Within each target are one or more commands that are displayed as XML Elements. There's no need to worry about the specific commands. The application will just look at each target, get its name, description, and identify any dependencies if they exist (neither of the targets in the example have any).

Although you most often run Ant from the command line, that can get a little tedious. The process requires you to be in the same directory as the build.xml file you want to use and then execute Ant while passing arguments that let it know which targets to run. Because a project can have any number of targets and you may not also want to run every target, you need to remain well-versed in what targets are available and what they are dependent on, and so on. As a result, it is sometimes helpful to use a graphical interface to manipulate Ant, and this is exactly what this set of classes will do.

The interface is divided into three panes. On the left is the Project List, which is a listing of all Ant project files (named build.xml, or something else) in a given directory that you have selected. In the middle column, the Available Targets List shows all the targets that have been defined in this project. The column on the right lists the targets that you want to execute in the order that you want to execute them. There is a Move button that can be clicked to move a selected target into the list for executing. You can also clear the execute list, or remove individual items in the list. Finally, you can execute the targets in the sequence specified and then view the results in an EditField.

In Figure 7.4, three projects are listed. The second project is selected, which causes all the targets in the build-svn.xml file to be displayed in the center ListBox. Two of the targets have been selected to be executed, and they are displayed in the right ListBox and will be executed in order:

Figure 7.4. Ant Shell.


add-pages and commit-pages. 


There are a few features you should pay special attention to. The first is trivial, but this application shows an example of how to colorize every other line of a ListBox, which is nice to know and can make them much easier to read. More important is the XmlReader subclass, which reads each build.xml file, identifies the targets that are available, and populates the ListBox accordingly. This is just a good, everyday kind of example of how reading XML files can be integrated into your application.

To use the Shell class in interactive or asynchronous mode, the instance has to remain in scope while it awaits a result. This means that you either need to drag the shell to the application Window and execute that way, or you need to create it as a property of something persistent, like a window. If you don't, and then instantiate it as a local variable within a method or event, it will go out of scope before returning any results, which is not what you want.

Following is an example of instantiating the AntShell class as a property of Window1 and executing a command:

Listing 7.25. Instantiating the AntShell class

If ant = Nil Then     ant = New AntShell     ant.setMode(2)     ant.setView(window1.EditField2) End If // The command is the executable to be run ant.setCmd "ant" // An "Opt" lets you set an option through the command line. // It consists of two parts, the first identifies the option // and the second establishes the value for that option. ant.addOpt "-f", "/Users/mchoate/Documents/Projects"_   + "/REALbasic/Metawrite/ant/build-svn.xml" // an "Arg" is a command line switch that consists of one // word or phrase ant.addArg "test" // Executes the command ant.exec 

In the actual Ant Shell example, the AntShell instance is not a property, but is treated like a control and resides on a Window subclass called AntTargetView. To launch the AntTargetView window, you do the following:

Dim av as AntTargetViewer Dim f as folderitem av = New AntTargetViewer(AntTargetView) // Select a folder that contains Ant build files f = selectfolder // List build files. av.getProjects(f) 


Although REALbasic makes it easy to extend Windows by adding methods to them, this example does not implement the methods used to control the Window in the Window itself. Instead, I use a separate class that serves as a controller. The Window is called AntTargetView, and the controller class is called AntTargetViewer.

AntTargetView Inherits Window

AntTargetView is a window, but it takes a somewhat different approach to managing the window. Instead of implementing methods in the Window itself, the methods are implemented in a separate "Viewer" class, which is associated with the Window as the controller property.

There are a few reason for doing it this way. The first is that it gives you more control and a way around the automatic Window instantiation "feature" that causes Windows to be viewed whenever members are accessed. Just move all that stuff to a different class and it no longer has that effect. It is also easier to subclass a class than to subclass a Window, and you can manage multiple Windows with one class if you want to, which makes it easier for the state of one Window to impact the state of another associated Window.

It has one property, which is a reference to the controlling class:

Property controller as AntTargetViewer 


Listing 7.26. Sub AntTargetView.DoubleClick() Handles Event

// Select a project and display the available targets Dim t as AntTarget t = ListBoxProjectList.CellTag(ListBoxProjectList.ListIndex, 0) ListBoxAvailable.addRow(t.name) ListBoxAvailable.CellTag(ListBoxAvailable.LastIndex, 0) = t 

Listing 7.27. Sub AntTargetView.PushButtonRun.Action()

// Execute ant targets in the order they are // listed in ListBoxOrder. Dim t as AntTarget Dim p as AntProject Dim x,y as Integer p = AntProject(ListBoxProject.celltag(ListBoxProject.ListIndex, 0)) // When running in asynchronous mode, you should check // to make sure the Shell is still running before // executing a command. If not antshell1.IsRunning Then     Antshell1.setMode(2)     Antshell1.setView(Window1.EditField2) // On Mac/Linux there is more than one Shell, so you will // need to be aware of which shell you are running and // it's individual idiosyncracies.     Antshell1.execute "sh" Else     AntShell1.reset End If  AntShell1.setCmd "ant"  AntShell1.addOpt "-f", p.build.ShellPath  y = ListBoxOrder.ListCount - 1  For x = 0 To y     t = AntTarget(listboxOrder.celltag(x, 0))     AntShell1.addArg t.name  Next  AntShell1.exec 

Listing 7.28. Sub AntTargetView.PushButtonOrder.Action()

// Clear ListBoxOrder and start over. ListBoxOrder.DeleteAllRows 

Listing 7.29. Sub AntTargetView.BevelButtonMove.Action()

// Selects and moves an available target into // ListBoxOrder so that it can be executed. Dim t as AntTarget t = ListBoxAvailable.CellTag(ListBoxAvailable.ListIndex, 0) ListBoxOrder.addRow(t.name) ListBoxOrder.CellTag(ListBoxOrder.LastIndex, 0) = t 

Listing 7.30. Sub AntTargetView.PushButtonClear.Action()

// Clear the selected target in ListBoxOrder ListBoxOrder.RemoveRow(ListBoxOrder.listindex) 

Listing 7.31. Function AntTargetView.ListBoxProjectList.CellBackgroundPaint(g As Graphics, row As Integer, column As Integer) As Boolean

// Colorize every other row of this ListBox If row Mod 2=0 Then     g.foreColor=RGB(224,224,255) Else     g.foreColor=RGB(255,255,255) End If     g.FillRect 0,0,g.width,g.height 

Listing 7.32. Function AntTargetView.ListBoxProjectList.CellClick(row as Integer, column as Integer, x as Integer, y as Integer) As Boolean

// Select a project Dim project as AntProject ListBoxAvailable.DeleteAllRows project = AntProject(listboxOrder.celltag(row,0)) If project <> Nil Then     StaticText4.text = project.description     controller.view(project) End If 

Listing 7.33. Class AntTargetViewer

// The view property is a reference to the // Window that this class controls Property view as AntTargetView // The list of projects displayed Property Projects(-1) as AntProject // Determines whether all projects // should be viewed. Property ShowProjects as Boolean 

Listing 7.34. Sub AntTargetViewer.view(project as AntProject)

// Display Projects in ListBoxProjectList Dim t as AntTarget view.ListBoxProjectList.DeleteAllRows If project <> Nil Then     // A project is a collection of targets     For each t in project.targets         view.ListBoxProjectList.addrow(t.name)         // Show targets in the second column         view.ListBoxProjectList.Cell(view.ListBoxProjectList.LastIndex, 1) _           = t.depends         view.ListBoxProjectList.celltag(view.ListBox1.LastIndex, 0) = t     Next End If 

Listing 7.35. Sub AntTargetViewer.Constructor(antView as AntTargetView)

Me.view = antView Me.view.controller = me ShowProjects = False 

Listing 7.36. Sub AntTargetViewer.getProject(aBuildFile as FolderItem)

// Instantiate XMLReader subclass and parse build file Dim ar as AntReader ar = New AntReader ar.viewer = me ar.parse(aBuildFile) 

Listing 7.37. Sub AntTargetViewer.handleProject(aProject as AntProject)

If ShowProjects = True Then     viewProjectList(aProject) Else     view(aProject) End If 

Listing 7.38. Sub AntTargetViewer.viewProjectList(project as AntProject)

// Display all projects that reside in the selected directory Dim t as AntTarget If project <> Nil Then     projects.append(project)     view.ListBoxProjectList.row(project.name)     view.ListBoxProjectList.cell(view.ListBoxProjectList.lastindex, 1) = project.build.name     view.ListBoxProjectList.celltag(view.ListBoxProjectList.lastindex, 0) = project End If 

Listing 7.39. Sub AntTargetViewer.getProjects(aDir as FolderItem)

// Get projects that reside in aDir Dim ar as AntReader Dim x,y as integer Me.ShowProjects = True view.ListBoxOrder.DeleteAllRows If aDir.Directory Then     y = aDir.Count - 1     For x = 0 To y        If aDir.item(x).IsReadable and aDir.item(x).Visible Then            If Right(aDir.item(x).name, 4) = ".xml"   Then               getProject(aDir.item(x))            End If        End If     Next End If 

Listing 7.40. Sub AntTargetViewer.Release()

Me.view = Nil 

Listing 7.41. AntProject

// A simple data-oriented class that holds // important information about a given project. Property name as String Property default as String Property basedir as String Property targets(-1) as AntTarget Property description as String Property build as folderitem 

Listing 7.42. AntTarget

// A simple data-oriented class that holds // important information about the target Property name as String Property depends as String Property description as String 

Listing 7.43. AntReader Inherits XMLReader

//The XMLReader subclass that parses the build files //and extracts project and target information. This //subclass takes the data from the XML file and //instantiates AntProject and AntTarget objects. Property Project as AntProject Property Viewer as AntTargetViewer // Build files are very simple, so this XMLReader // subclass does not have to work to hard at // maintaining state. It tracks whether the parser is // inside a project element or inside a target element. Property inProject as Boolean Property inTarget as Boolean Property getText as Boolean Property build as FolderItem 

Listing 7.44. Sub AntReader.StartElement(name as String, attributeList as XmlAttributeList) Handles Event

// Most of the work is done in the StartElement event. // Namespaces are not expanded and the properties // for the AntProject instance come from XML attributes Dim t as AntTarget Select Case name // Create an AntProject Case "project"     inProject = True     Project = New AntProject     Project.name = attributeList.value("name")     Project.default = attributeList.value("default")     Project.basedir = attributeList.value("basedir")     Project.description = attributeList.value("description") // Create an AntTarget Case "target"     inTarget = True     t = New AntTarget     t.name = attributeList.value("name")     t.depends = attributeList.value("depends")     t.description = attributeList.value("description")     Project.targets.append(t) Case "description"     // Only get descriptions for projects.     If inProject = True and inTarget = False Then         getText = True     End If End select 

Listing 7.45. Sub AntReader.Characters(s as String) Handles Event

// If inProject and inTarget are true, then getText // is set to true in the StartElement event handler // if the current element is named "description" // This tells the AntReader to capture the text // and set the description property of AntProject // Since there can theoretically be sequence Characters // events called, I concatenate the new text with the // potentially existing text. If getText = True Then     Project.description = Project.description + s End If 

Listing 7.46. Sub AntReader.EndDocument() Handles Event

// Done parsing; handle it If Me.build <> Nil Then     project.build = me.build End If Viewer.handleProject(Project) 

Listing 7.47. Sub AntReader.parse(aBuildFile as folderitem)

Me.build = aBuildFile super.parse(aBuildFile) 

Listing 7.48. AntShell Inherits mwShell

Property AntHome as String Property BuildFile as String Property Target as String Property JavaHome as String 

Listing 7.49. Sub AntShell.DataAvailable() Handles Event

print me.ReadAll 

Listing 7.50. Sub AntShell.Constructor()

// Override mwShell default Constructor. 

Listing 7.51. Sub AntShell.execTarget(aTarget as String)

// The -f option lets you specify the name of a build file. // Without it, Ant defaults to executing the file // called build.xml addOpt "-f", BuildFile addArg aTarget exec 

Listing 7.52. Sub AntShell.setAntHome(aHome as String)

// Ant requires that an environment variable be set. // Just because you have environment variables set // up so that when you log-in, they are available, // do not assume that they will be available when // using REALbasic's shell, even though it is // running under your name. Be sure to check and // set the variables you need set, this is especially // important because command line applications // often make heavy use of environment variables. If aHome <> "" Then     AntHome = aHome Else     AntHome = "/Users/mchoate/ant"     System.EnvironmentVariable("ANT_HOME")=AntHome End If 

Listing 7.53. Sub AntShell.setJavaHome(aPath as String)

// Set the JAVA_HOME environement variable Dim tmp as String tmp = System.EnvironmentVariable("JAVA_HOME") If aPath = "" Then     If tmp = "" Then        #If TargetMachO Then            System.EnvironmentVariable("JAVA_HOME") = "/Library/Java/Home"        #endIf     End If Else     If tmp = "" Then        System.EnvironmentVariable("JAVA_HOME") = aPath     End If End If 

Listing 7.54. Sub AntShell.setBuildFile(aFile as String)

If aFile <> "" Then     BuildFile = aFile End If 

Listing 7.55. Sub AntShell.setTarget(aTarget as String)

If aTarget <> "" Then     target = atarget End If 




REALbasic Cross-Platform Application Development
REALbasic Cross-Platform Application Development
ISBN: 0672328135
EAN: 2147483647
Year: 2004
Pages: 149

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net