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 PropertiesWhether the Shell is synchronous, asynchronous, or interactive is determined by the Mode property: Shell.Mode as Integer The values you can use are
These values can be set at runtime. Synchronous ExecutionThe 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 ExecutionThe 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 EventsThe 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 MethodsYou 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 ShellIn 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 ShellmwShell 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)
Listing 7.2. Sub mwShell.addArg(myArg as String)
Listing 7.3. Sub mwShell.addOpt(option as String, arg as String)
Listing 7.4. Sub mwShell.setCmd(command as String)
Listing 7.5. Sub mwShell.exec()
Listing 7.6. Sub mwShell.print(s as String)
Listing 7.7. Sub mwShell.setPath(path as String, setPathType as Integer)
Listing 7.8. Sub mwShell.setMode(aMode as Integer)
Listing 7.9. mwShell.Sub setView(anEditField as EditField)
Listing 7.10. Sub mwShell.setWorkingDirectory(aPath as String)
Listing 7.11. Sub mwShell.reset()
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. SubversionAs 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 mwShellIn 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
Listing 7.13. Sub SVNclient.DataAvailable() Handles Event
Listing 7.14. Sub SVNclient.status(source_String as String)
Listing 7.15. Sub SVNclient.add(source_path as String)
Listing 7.16. Sub SVNclient.delete(source_path as String)
Listing 7.17. Sub SVNclient.update(source_String as String)
Listing 7.18. Sub SVNclient.commit(source_String as String, msg as String)
Listing 7.19. Sub SVNclient.log()
Listing 7.20. Sub SVNclient.setUser(aUser as String, aPassword as String)
Listing 7.21. Window1 Inherits Window
Listing 7.22. Sub Window1.PushButtonSVN.Action() Handles Event
Listing 7.23. Sub Window1.PushButtonExec.Action() Handles Event
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 ShellAnt 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
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
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 WindowAntTargetView 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
Listing 7.27. Sub AntTargetView.PushButtonRun.Action()
Listing 7.28. Sub AntTargetView.PushButtonOrder.Action()
Listing 7.29. Sub AntTargetView.BevelButtonMove.Action()
Listing 7.30. Sub AntTargetView.PushButtonClear.Action()
Listing 7.31. Function AntTargetView.ListBoxProjectList.CellBackgroundPaint(g As Graphics, row As Integer, column As Integer) As Boolean
Listing 7.32. Function AntTargetView.ListBoxProjectList.CellClick(row as Integer, column as Integer, x as Integer, y as Integer) As Boolean
Listing 7.33. Class AntTargetViewer
Listing 7.34. Sub AntTargetViewer.view(project as AntProject)
Listing 7.35. Sub AntTargetViewer.Constructor(antView as AntTargetView)
Listing 7.36. Sub AntTargetViewer.getProject(aBuildFile as FolderItem)
Listing 7.37. Sub AntTargetViewer.handleProject(aProject as AntProject)
Listing 7.38. Sub AntTargetViewer.viewProjectList(project as AntProject)
Listing 7.39. Sub AntTargetViewer.getProjects(aDir as FolderItem)
Listing 7.40. Sub AntTargetViewer.Release()
Listing 7.41. AntProject
Listing 7.42. AntTarget
Listing 7.43. AntReader Inherits XMLReader
Listing 7.44. Sub AntReader.StartElement(name as String, attributeList as XmlAttributeList) Handles Event
Listing 7.45. Sub AntReader.Characters(s as String) Handles Event
Listing 7.46. Sub AntReader.EndDocument() Handles Event
Listing 7.47. Sub AntReader.parse(aBuildFile as folderitem)
Listing 7.48. AntShell Inherits mwShell
Listing 7.49. Sub AntShell.DataAvailable() Handles Event
Listing 7.50. Sub AntShell.Constructor()
Listing 7.51. Sub AntShell.execTarget(aTarget as String)
Listing 7.52. Sub AntShell.setAntHome(aHome as String)
Listing 7.53. Sub AntShell.setJavaHome(aPath as String)
Listing 7.54. Sub AntShell.setBuildFile(aFile as String)
Listing 7.55. Sub AntShell.setTarget(aTarget as String)
|