This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] • Table of Contents • Index • Examples Practical Programming in Tcl and Tk, Fourth Edition By Brent B. Welch, Ken Jones, Jeffrey Hobbs Publisher: Prentice Hall PTR Pub Date: June 10, 2003 ISBN: 0-13-038560-3 Pages: 960 Practical Programming in Tcl/Tk is described as the "bible" for Tcl programmers. It is a guide to the Tcl/Tk programming language and GUI toolkit. This revision includes substantial updates to cover the new version 8.4-giving both an overview of the features, as well as details about every command in the language. The third edition, written on version 8.2, sold over 30,000 copies. Version 8.4 of Tcl - Tool Command Language-provides substantial updates to one of the most popular UNIX scripting languages. The latest release, includes the addition of a virtual filesystem (VFS), many additional programming widgets (spinbox, panedwindow, labelframe),and improved performance of about 20% over 8.3. The book provides a guide to the best ways to use the tooklit. It not only gives accurate details, but includes extensive examples that demonstrate the best way to use the toolkit. The authors are experts that have both developed the technology and used it to solve problems, so they have many valuable insights to relate to the readers. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it.. Thanks [ Team LiB ] • Table of Contents • Index • Examples Practical Programming in Tcl and Tk, Fourth Edition By Brent B. Welch, Ken Jones, Jeffrey Hobbs Publisher: Prentice Hall PTR Pub Date: June 10, 2003 ISBN: 0-13-038560-3 Pages: 960 Copyright List of Examples List of Tables Preface Why Tcl? Tcl and Tk Versions Extending Tcl and Tk Tcl on the World Wide Web Ftp Archives Newsgroups Who Should Read This Book How to Read This Book On-line Examples Typographic Conventions Hot Tips Book Organization What's New in the Fourth Edition Other Tcl Books First Edition Thanks Second Edition Thanks This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Third Edition Thanks Fourth Edition Thanks Contact the Author Part I. Tcl Basics Chapter 1. Tcl Fundamentals Tcl Commands Hello, World! Variables Command Substitution Math Expressions Backslash Substitution Grouping with Braces and Double Quotes Procedures A Factorial Example More about Variables More about Math Expressions Comments Substitution and Grouping Summary Fine Points Reference Chapter 2. Getting Started The source Command UNIX Tcl Scripts Windows Start Menu Macintosh OS 8/9 andResEdit The console Command Command-Line Arguments Predefined Variables Chapter 3. The Guestbook CGI Application A Quick Introduction to HTML CGI for Dynamic Pages The guestbook.cgi Script Defining Forms and Processing Form Data Handling Errors in CGI Scripts Next Steps Chapter 4. String Processing in Tcl The string Command The append Command The format Command The scan Command The binary Command Related Chapters Chapter 5. Tcl Lists Tcl Lists Constructing Lists This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Getting List Elements: llength, lindex, and lrange Modifying Lists: linsert and lreplace Searching Lists: lsearch Sorting Lists: lsort The split Command The join Command Related Chapters Chapter 6. Control Structure Commands If Then Else Switch While Foreach For Break and Continue Catch Error Return Chapter 7. Procedures and Scope The proc Command Changing Command Names with rename Scope The global Command Call by Name Using upvar Variable Aliases with upvar Chapter 8. Tcl Arrays Array Syntax The array Command Building Data Structures with Arrays Chapter 9. Working with Files and Programs Running Programs with exec The file Command Cross-Platform File Naming Manipulating Files and Directories File Attributes Input/Output Command Summary Opening Files for I/O Reading and Writing The Current Directory ? cd and pwd Matching File Names with glob The exit and pid Commands Environment Variables The registry Command Part II. Advanced Tcl Chapter 10. Quoting Issues and Eval Constructing Code with the list Command This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Exploiting the concat inside eval The uplevel Command The subst Command Chapter 11. Regular Expressions When to Use Regular Expressions Regular Expression Syntax Advanced Regular Expressions Syntax Summary The regexp Command The regsub Command Transforming Data to Program with regsub Other Commands That Use Regular Expressions Chapter 12. Script Libraries and Packages Locating Packages: The auto_path Variable Using Packages Summary of Package Loading The package Command Libraries Based on the tclIndex File The unknown Command Interactive Conveniences Tcl Shell Library Environment Coding Style Chapter 13. Reflection and Debugging The clock Command The info Command Cross-Platform Support Tracing Variables and Commands Interactive Command History Debugging Tcl Dev Kit Other Tools Performance Tuning Chapter 14. Namespaces Using Namespaces Namespace Variables Command Lookup Nested Namespaces Importing and Exporting Procedures Callbacks and Namespaces Introspection The namespace Command Converting Existing Packages to use Namespaces [incr Tcl] Object System xotcl Object System Notes This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Chapter 15. Internationalization Character Sets and Encodings Message Catalogs Chapter 16. Event-Driven Programming The Tcl Event Loop The after Command The fileevent Command The vwait Command The fconfigure Command Chapter 17. Socket Programming Networking Extensions for Tcl Client Sockets Server Sockets The Echo Service Fetching a URL with HTTP The http Package Basic Authentication Chapter 18. TclHttpd Web Server Integrating TclHttpd with Your Application Domain Handlers Application Direct URLs Document Types HTML + Tcl Templates Form Handlers Programming Reference Standard Application Direct URLs The TclHttpd Distribution Server Configuration Chapter 19. Multiple Interpreters and Safe-Tcl The interp Command Creating Interpreters Safe Interpreters Command Aliases Hidden Commands Substitutions I/O from Safe Interpreters The Safe Base Security Policies Chapter 20. Safe-Tk and the Browser Plugin Tk in Child Interpreters The Browser Plugin Security Policies and Browser Plugin Configuring Security Policies Chapter 21. Multi-Threaded Tcl Scripts What are Threads? This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Thread Support in Tcl Getting Started with the Thread Extension Sending Messages to Threads Preserving and Releasing Threads Error Handling Shared Resources Managing I/O Channels Shared Variables Mutexes and Condition Variables Thread Pools The Thread Package Commands Chapter 22. Tclkit and Starkits Getting Started with Tclkit Virtual File Systems Using sdx to Bundle Applications Exploring the Virtual File System in a Starkit Creating tclhttpd.kit Creating a Shared Starkit Metakit More Ideas Part III. Tk Basics Chapter 23. Tk Fundamentals Hello, World! in Tk Naming Tk Widgets Configuring Tk Widgets Tk Widget Attributes and the Resource Database Summary of the Tk Commands Other Widget Sets Chapter 24. Tk by Example ExecLog The Example Browser A Tcl Shell Chapter 25. The Pack Geometry Manager Packing toward a Side Horizontal and Vertical Stacking The Cavity Model Packing Space and Display Space Resizing and -expand Anchoring Packing Order Choosing the Parent for Packing Unpacking a Widget Packer Summary Window Stacking Order Chapter 26. The Grid Geometry Manager This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. A Basic Grid Spanning Rows and Columns Row and Column Constraints The grid Command Chapter 27. The Place Geometry Manager place Basics The Pane Manager The place Command Chapter 28. The Panedwindow Widget Using the Panedwindow Programming Panedwindow Widgets Panedwindow Attributes Chapter 29. Binding Commands to Events The bind Command The bindtags Command Event Syntax Modifiers Event Sequences Virtual Events Generating Events Event Summary Part IV. Tk Widgets Chapter 30. Buttons and Menus Button Commands and Scope Issues Buttons Associated with Tcl Variables Button Attributes Button Operations Menus and Menubuttons Menu Bindings and Events Manipulating Menus and Menu Entries Menu Attributes A Menu by Name Package Chapter 31. The Resource Database An Introduction to Resources Loading Option Database Files Adding Individual Database Entries Accessing the Database User-Defined Buttons User-Defined Menus Chapter 32. Simple Tk Widgets Frames, Labelframes, and Toplevel Windows The Label Widget The Message Widget The Scale Widget The bell Command This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Chapter 33. Scrollbars Using Scrollbars The Scrollbar Protocol The Scrollbar Widget Chapter 34. The Entry and Spinbox Widgets Using Entry Widgets Using Spinbox Widgets Entry and Spinbox Bindings Entry and Spinbox Attributes Programming Entry and Spinbox Widgets Chapter 35. The Listbox Widget Using Listboxes The Listbox Widget Listbox Bindings and Events Listbox Attributes Chapter 36. The Text Widget Text Indices Text Marks Text Tags The Selection Tag Bindings Searching Text Embedded Widgets Embedded Images Looking inside the Text Widget The Undo Mechanism Text Bindings and Events Text Operations Text Attributes Chapter 37. The Canvas Widget Canvas Coordinates Hello, World! The Min Max Scale Example Canvas Objects Canvas Operations Generating Postscript Canvas Attributes Hints Part V. Tk Details Chapter 38. Selections and the Clipboard> The Selection Model The selection Command The clipboard Command Selection Handlers Chapter 39. Focus, Grabs, and Dialogs This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Standard Dialogs Custom Dialogs Animation with the update Command Chapter 40. Tk Widget Attributes Configuring Attributes Size Borders and Relief The Focus Highlight Padding and Anchors Chapter 41. Color, Images, and Cursors Colors Colormaps and Visuals Bitmaps and Images The Text Insert Cursor The Mouse Cursor Chapter 42. Fonts and Text Attributes Naming a Font X Font Names Font Metrics The font Command Text Attributes Gridding, Resizing, and Geometry A Font Selection Application Chapter 43. Send The send Command The Sender Script Communicating Processes Remote eval through Sockets Chapter 44. Window Managers and Window Information The wm Command The winfo Command The tk Command Chapter 45. Managing User Preferences App-Defaults Files Defining Preferences The Preferences User Interface Managing the Preferences File Tracing Changes to Preference Variables Improving the Package Chapter 46. A User Interface to Bindings A Pair of Listboxes Working Together The Editing Interface Saving and Loading Bindings Part VI. C Programming Chapter 47. C Programming and Tcl This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Basic Concepts Creating a Loadable Package A C Command Procedure The blob Command Example CONST in the Tcl 8.4 APIs Strings and Internationalization Tcl_Main and Tcl_AppInit The Event Loop Invoking Scripts from C Chapter 48. Compiling Tcl and Extensions Standard Directory Structure Building Tcl from Source Using Stub Libraries Using autoconf The Sample Extension Chapter 49. Writing a Tk Widget in C Initializing the Extension The Widget Data Structure The Widget Class Command The Widget Instance Command Configuring and Reconfiguring Attributes Specifying Widget Attributes Displaying the Clock The Window Event Procedure Final Cleanup Chapter 50. C Library Overview An Overview of the Tcl C Library An Overview of the Tk C Library Part VII. Changes Chapter 51. Tcl 7.4/Tk 4.0 wish Obsolete Features The cget Operation Input Focus Highlight Bindings Scrollbar Interface pack info Focus The send Command Internal Button Padding Radiobutton Value Entry Widget Menus Listboxes No geometry Attribute This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Text Widget Color Attributes Color Allocation and tk colormodel Canvas scrollincrement The Selection The bell Command Chapter 52. Tcl 7.5/Tk 4.1 Cross-Platform Scripts The clock Command The load Command The package Command Multiple foreach loop variables Event Loop Moves from Tk to Tcl Network Sockets Multiple Interpreters and Safe-Tcl The grid Geometry Manager The Text Widget The Entry Widget Chapter 53. Tcl 7.6/Tk 4.2 More file Operations Virtual Events Standard Dialogs New grid Geometry Manager Macintosh unsupported1 Command Chapter 54. Tcl/Tk 8.0 The Tcl Compiler Namespaces Safe-Tcl New lsort tcl_precision Variable Year 2000 Convention Http Package Serial Line I/O Platform-Independent Fonts The tk scaling Command Application Embedding Native Menus and Menubars CDE Border Width Native Buttons and Scrollbars Images in Text Widgets No Errors from destroy grid rowconfigure The Patch Releases Chapter 55. Tcl/Tk 8.1 Unicode and Internationalization This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Thread Safety Advanced Regular Expressions New String Commands The DDE Extension Miscellaneous Chapter 56. Tcl/Tk 8.2 The Trf Patch Faster String Operations Empty Array Names Browser Plugin Compatibility Finer Control of Windows Serial Port Monitoring Regular Expression Expanded Syntax Option Chapter 57. Tcl/Tk 8.3 New File Manipulation Commands and Options New glob Options Regular Expression Command Enhancements Direct Return of scan Matches Removing Duplicate List Elements withlsort Deleting Elements from an Array Enhanced clock Features Support for Delayed Package Loading inpkg_mkIndex The Img Patch The Dash Patch Other New Tk Features The Patch Releases Chapter 58. Tcl/Tk 8.4 64-Bit Support Additional Filesystem Features and Commands New and Enhanced List Commands Array Searching and Statistics Enhanced Support for Serial Communications New String Comparison Operators Command Tracing Additional Introspection Commands Other Tcl Changes New Tk Widgets Text Widget Undo Mechanism and Other Enhancements New pack and grid Features Displaying Both Text and an Image in a Widget New Button Relief Attributes Controlling the State of Entries and Listboxes More Window Manager Interaction Other Tk Changes Chapter 59. About The CD-ROM Technical Support This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Index [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Copyright Library of Congress Cataloging-in-Publication available Editorial/production supervision: Kathleen M. Caren Executive Editor: Mark Taub Editorial Assistant: Noreen Regina Marketing Manager: Kate Hargett Manufacturing Manager: Maura Zaldivar Cover Design Director: Jerry Votta © 2003 Pearson Education Inc. Publishing as Prentice Hall PTR Upper Saddle River, NJ 07458 Prentice Hall books are widely used by corporations and government agencies for training, marketing, and resale. For information regarding corporate and government bulk discounts, contact: Corporate and Government Sales: (800) 382-3419 or corpsales@pearsontechgroup.com All products mentioned herein are trademarks or registered trademarks of their respective owners. All rights reserved. No part of this book may be reproduced, in any form or by any means, without permission in writing from the publisher. Printed in the United States of America 10 9 8 7 6 5 4 3 2 1 Pearson Education LTD. Pearson Education Australia PTY, Limited Pearson Education Singapore, Pte. Ltd. Pearson Education North Asia Ltd. Pearson Education Canada, Ltd. Pearson Educación de Mexico, S.A. de C.V. Pearson Education—Japan Pearson Education Malaysia, Pte. Ltd. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Dedication to Jody, Christopher, Daniel, and Michael —Brent Dean, for his support and patience —Ken [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] List of Examples 1. Tcl Fundamentals 1.1 The "Hello, World!" example 1.2 Tcl variables 1.3 Command substitution 1.4 Simple arithmetic 1.5 Nested commands 1.6 Built-in math functions 1.7 Grouping expressions with braces 1.8 Quoting special characters with backslash 1.9 Continuing long lines with backslashes 1.10 Grouping with double quotes vs. braces 1.11 Embedded command and variable substitution 1.12 Defining a procedure 1.13 A while loop to compute factorial 1.14 A recursive definition of factorial 1.15 Using set to return a variable value 1.16 Embedded variable references 1.17 Using info to determine if a variable exists 1.18 Controlling precision with tcl_precision 2. Getting Started This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 2.1 A standalone Tcl script on UNIX 2.2 A standalone Tk script on UNIX 2.3 Using /bin/sh to run a Tcl script 2.4 The EchoArgs script 3. The Guestbook CGI Application 3.1 A simple CGI script 3.2 Output of Example 3-1 3.3 The guestbook.cgi script, version 1 3.4 The Cgi_Header procedure 3.5 The guestbook.cgi script, version 2 3.6 Initial output of guestbook.cgi with no data 3.7 Output of guestbook.cgi with guestbook data 3.8 The newguest.html form 3.9 The newguest.cgi script 3.10 The newguest.cgi script with error handling 4. String Processing in Tcl 4.1 Comparing strings with string compare 4.2 Comparing strings with string equal 4.3 Comparing strings with eq 4.4 Mapping Microsoft World special characters to ASCII 5. Tcl Lists 5.1 Constructing a list with the list command 5.2 Using lappend to add elements to a list 5.3 Using lset to set an element of a list This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 5.4 Using concat to splice lists together 5.5 Double quotes compared to the concat and list commands 5.6 Modifying lists with lreplace 5.7 Deleting a list element by value 5.8 Sorting a list using a comparison function 5.9 Use split to turn input data into Tcl lists 5.10 Implementing join in Tcl 6. Control Structure Commands 6.1 A conditional if then else command 6.2 Chained conditional with elseif 6.3 Using switch for an exact match 6.4 Using switch with substitutions in the patterns 6.5 A switch with "fall through" cases 6.6 Comments in switch commands 6.7 A while loop to read standard input 6.8 Looping with foreach 6.9 Parsing command-line arguments 6.10 Using list with foreach 6.11 Multiple loop variables with foreach 6.12 Multiple value lists with foreach 6.13 A for loop 6.14 A standard catch phrase 6.15 A longer catch phrase 6.16 There are several possible return values from catch This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 6.17 Raising an error 6.18 Preserving errorInfo when calling error 6.19 Raising an error with return 7. Procedures and Scope 7.1 Default parameter values 7.2 Variable number of arguments 7.3 Variable scope and Tcl procedures 7.4 A random number generator 7.5 Print variable by name 7.6 Improved incr procedure 8. Tcl Arrays 8.1 Using arrays 8.2 Referencing an array indirectly 8.3 Referencing an array indirectly using upvar 8.4 ArrayInvert inverts an array 8.5 Using arrays for records, version 1 8.6 Using arrays for records, version 2 8.7 Using arrays for records, version 3 8.8 Using a list to implement a stack 8.9 Using an array to implement a stack 8.10 A list of arrays 8.11 A list of arrays 8.12 A simple in-memory database 9. Working with Files and Programs 9.1 Using exec on a process pipeline This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 9.2 Comparing file modify times 9.3 Determining whether pathnames reference the same file 9.4 Opening a file for writing 9.5 A more careful use of open 9.6 Opening a process pipeline 9.7 Prompting for input 9.8 A read loop using gets 9.9 A read loop using read and split 9.10 Copy a file and translate to native format 9.11 Finding a file by name 9.12 Printing environment variable values 10. Quoting Issues and Eval 10.1 Using list to construct commands 10.2 Generating procedures dynamically with a template 10.3 Using eval with $args 10.4 lassign: list assignment with foreach 10.5 The File_Process procedure iterates over lines in a file 11. Regular Expressions 11.1 Expanded regular expressions allow comments 11.2 Using regular expressions to parse a string 11.3 A pattern to match URLs 11.4 An advanced regular expression to match URLs 11.5 The Url_Decode procedure 11.6 The Cgi_List and Cgi_Query procedures 11.7 Cgi_Parse and Cgi_Value store query data in the cgi array This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 11.8 Html_DecodeEntity 11.9 Html_Parse 12. Script Libraries and Packages 12.1 Maintaining a tclIndex file 12.2 Loading a tclIndex file 13. Reflection and Debugging 13.1 Calculating clicks per second 13.2 Printing a procedure definition 13.3 Mapping form data onto procedure arguments 13.4 Finding built-in commands 13.5 Getting a trace of the Tcl call stack 13.6 A procedure to read and evaluate commands 13.7 Using info script to find related files 13.8 Tracing variables 13.9 Creating array elements with array traces 13.10 Interactive history usage 13.11 Implementing special history syntax 13.12 A Debug procedure 13.13 Time Stamps in log records 14. Namespaces 14.1 Random number generator using namespaces 14.2 Random number generator using qualified names 14.3 Nested namespaces 14.4 The code procedure to wrap callbacks 14.5 Listing commands defined by a namespace This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 15. Internationalization 15.1 MIME character sets and file encodings 15.2 Using scripts in nonstandard encodings 15.3 Three sample message catalog files 15.4 Using msgcat::mcunknown to share message catalogs 16. Event-Driven Programming 16.1 A read event file handler 16.2 Using vwait to activate the event loop 16.3 A read event file handler for a nonblocking channel 17. Socket Programming 17.1 Opening a client socket with a timeout 17.2 Opening a server socket 17.3 The echo service 17.4 A client of the echo service 17.5 Opening a connection to an HTTP server 17.6 Opening a connection through a HTTP proxy 17.7 Http_Head validates a URL 17.8 Using Http_Head 17.9 Http_Get fetches the contents of a URL" endterm="ch17list09.title"/> 17.10 HttpGetText reads text URLs 17.11 HttpCopyDone is used with fcopy 17.12 Downloading files with http::geturl 17.13 Basic Authentication using http::geturl 18. TclHttpd Web Server 18.1 The hello.tcl file implements /hello/world This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 18.2 A simple URL domain 18.3 Application Direct URLs 18.4 Alternate types for Application Direct URLs 18.5 A sample document type handler 18.6 A one-level site structure 18.7 A two-level site structure 18.8 A HTML + Tcl template file 18.9 SitePage template procedure, version 1 18.10 SiteMenu and SiteFooter template procedures 18.11 The SiteLink procedure 18.12 Mail form results with /mail/forminfo 18.13 Mail message sent by /mail/forminfo 18.14 Processing mail sent by /mail/forminfo 18.15 Processing mail sent by /mail/forminfo, Safe-Tcl version 18.16 A self-checking form procedure 18.17 A page with a self-checking form 18.18 Generating a table with html::foreach 18.19 The /debug/source Application Direct URL implementation 19. Multiple Interpreters and Safe-Tcl 19.1 Creating and deleting an interpreter 19.2 Creating a hierarchy of interpreters 19.3 A command alias for exit 19.4 Querying aliases 19.5 Dumping aliases as Tcl commands 19.6 Substitutions and hidden commands This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 19.7 Opening a file for an unsafe interpreter 19.8 The Safesock security policy 19.9 The Tempfile security policy 19.10 Restricted puts using hidden commands 19.11 A safe after command 20. Safe-Tk and the Browser Plugin 20.1 Using EMBED to insert a Tclet 21. Multi-Threaded Tcl Scripts 21.1 Creating a separate thread to perform a lengthy operation 21.2 Initializing a thread before entering its event loop 21.3 Creating several threads in an application 21.4 Using joinable threads to detect thread termination 21.5 Examples of synchronous message sending 21.6 Using a return variable with synchronous message sending 21.7 Executing commands after thread::wait returns 21.8 Creating a custom thread error handler 21.9 A basic implementation of a logging thread 21.10 Deferring socket transfer until after the connection callback 21.11 Working around Tcl's socket transfer bug 21.12 A multi-threaded echo server 21.13 Using a mutex to protect a shared resource 21.14 Standard condition variable use for a signalling thread 21.15 Standard condition variable use for a waiting thread 22. Tclkit and Starkits 22.1 Accessing a Zip file through a VFS This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 22.2 The output of sdx lsk hello.kit 22.3 The main program of a Starkit 22.4 The pkgIndex.tcl in a Starkit 22.5 A Starkit that examines its Virtual File System 22.6 Creating a simple Starkit 22.7 The contents of the tclhttpd.vfs directory, version 1 22.8 The main program for the TclHttpd Starkit, version 1 22.9 Contents of the tclhttpd.vfs directory, version 2 22.10 The main program for the TclHttpd Starkit, version 2 22.11 The Standard Tcl Library Starkit main.tcl file 22.12 The main program for TclHttpd Starkit, version 3 22.13 Examining the views in a Metakit database 22.14 Examining data in a Metakit view 22.15 Selecting data with mk::select 22.16 Creating a new view 22.17 Adding data to a view 22.18 Storing data in a Starkit 23. Tk Fundamentals 23.1 "Hello, World!" Tk program 23.2 Looking at all widget attributes 24. Tk by Example 24.1 Logging the output of a program run with exec 24.2 A platform-specific cancel event 24.3 A browser for the code examples in the book 24.4 A Tcl shell in a text widget This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 24.5 Macintosh look and feel 24.6 Windows look and feel 24.7 UNIX look and feel 25. The Pack Geometry Manager 25.1 Two frames packed inside the main frame 25.2 Turning off geometry propagation 25.3 A horizontal stack inside a vertical stack 25.4 Even more nesting of horizontal and vertical stacks 25.5 Mixing bottom and right packing sides 25.6 Filling the display into extra packing space 25.7 Using horizontal fill in a menu bar 25.8 The effects of internal padding (-ipady) 25.9 Button padding vs. packer padding 25.10 The look of a default button 25.11 Resizing without the expand option 25.12 Resizing with expand turned on 25.13 More than one expanding widget 25.14 Setup for anchor experiments 25.15 The effects of noncenter anchors 25.16 Animating the packing anchors 25.17 Controlling the packing order 25.18 Packing into other relatives 26. The Grid Geometry Manager 26.1 A basic grid 26.2 A grid with sticky settings This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks 26.3 A grid with row and column specifications 26.4 A grid with external padding 26.5 A grid with internal padding 26.6 All combinations of -sticky settings 26.7 Explicit row and column span 26.8 Grid syntax row and column span 26.9 Row padding compared to cell padding 26.10 Gridding a text widget and scrollbar 26.11 Uniform column width 27. The Place Geometry Manager 27.1 Centering a window with place 27.2 Covering a window with place 27.3 Combining relative and absolute sizes 27.4 Positioning a window above a sibling with place 27.5 Pane_Create sets up vertical or horizontal panes 27.6 PaneDrag adjusts the percentage 27.7 PaneGeometry updates the layout 28. The Panedwindow Widget 28.1 A panedwindow with complex managed widgets 29. Binding Commands to Events 29.1 Bindings on different binding tags 29.2 Output from the UNIX xmodmap program 29.3 Emacs-like binding convention for Meta and Escape 29.4 Virtual events for cut, copy, and paste 30. Buttons and Menus This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks 30.1 A troublesome button command 30.2 Fixing the troublesome situation 30.3 A button associated with a Tcl procedure 30.4 Radiobuttons and checkbuttons 30.5 A command on a radiobutton or checkbutton 30.6 A menu sampler 30.7 A menu bar in Tk 8.0 30.8 Using the <<MenuSelect>> virtual event 30.9 A simple menu by name package 30.10 Using the Tk 8.0 menu bar facility 30.11 MenuGet maps from name to menu 30.12 Adding menu entries 30.13 A wrapper for cascade entries 30.14 Using the menu by name package 30.15 Keeping the accelerator display up to date 31. The Resource Database 31.1 Reading an option database file 31.2 A file containing resource specifications 31.3 Using resources to specify user-defined buttons 31.4 Resource_ButtonFrame defines buttons based on resources 31.5 Using Resource_ButtonFrame 31.6 Specifying menu entries via resources 31.7 Defining menus from resource specifications 31.8 Resource_GetFamily merges user and application resources 32. Simple Tk Widgets This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks 32.1 Labelframe example 32.2 Using the labelAnchor option to position a labelframe's anchor 32.3 Associating an existing label widget with a labelframe 32.4 Macintosh window styles 32.5 A label that displays different strings 32.6 The message widget formats long lines of text 32.7 Controlling the text layout in a message widget 32.8 A scale widget 33. Scrollbars 33.1 A text widget and two scrollbars 33.2 Scroll_Set manages optional scrollbars 33.3 Listbox with optional scrollbars 34. The Entry and Spinbox Widgets 34.1 Associating entry widgets with variables and commands 34.2 Restricting entry text to integer values 34.3 Reestablishing validation using an idle task 34.4 A simple spinbox with calculated values 34.5 Formatting numeric values in a spinbox 34.6 Enumerating spinbox values and wrapping 34.7 Using the spinbox readonly state 35. The Listbox Widget 35.1 Using -listvariable to link a listbox and variable 35.2 Choosing items from a listbox 35.3 Using the <<ListboxSelect>> virtual event 36. The Text Widget This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 36.1 Tag configurations for basic character styles 36.2 Line spacing and justification in the text widget 36.3 An active text button 36.4 Delayed creation of embedded widgets 36.5 Using embedded images for a bulleted list 36.6 Finding the current range of a text tag 36.7 Dumping the text widget 36.8 Dumping the text widget with a command callback 37. The Canvas Widget 37.1 A large scrolling canvas 37.2 The canvas "Hello, World!" example 37.3 A min max scale canvas example 37.4 Moving the markers for the min max scale 37.5 Canvas arc items 37.6 Canvas bitmap items 37.7 Canvas image items 37.8 A canvas stroke drawing example 37.9 Canvas oval items 37.10 Canvas polygon items 37.11 Dragging out a box 37.12 Simple edit bindings for canvas text items 37.13 Using a canvas to scroll a set of widgets 37.14 Generating Postscript from a canvas 38. Selections and the Clipboard 38.1 Paste the PRIMARY or CLIPBOARD selection This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 38.2 Separate paste actions 38.3 Bindings for canvas selection 38.4 Selecting objects 38.5 A canvas selection handler 38.6 The copy and cut operations 38.7 Pasting onto the canvas 39. Focus, Grabs, and Dialogs 39.1 Procedures to help build dialogs 39.2 A simple dialog 39.3 A feedback procedure 40. Tk Widget Attributes 40.1 Equal-sized labels 40.2 3D relief sampler 40.3 Padding provided by labels and buttons 40.4 Anchoring text in a label or button 40.5 Borders and padding 41. Color, Images, and Cursors 41.1 Resources for reverse video 41.2 Computing a darker color 41.3 Specifying an image for a widget 41.4 Specifying a bitmap for a widget 41.5 The built-in bitmaps 41.6 The Tk cursors 42. Fonts and Text Attributes 42.1 The FontWidget procedure handles missing fonts This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks 42.2 Font metrics 42.3 A gridded, resizable listbox 42.4 Font selection dialog 43. Send 43.1 The sender application 43.2 Hooking the browser to an eval server 43.3 Making the shell into an eval server 43.4 Remote eval using sockets 43.5 Reading commands from a socket 43.6 The client side of remote evaluation 44. Window Managers and Window Information 44.1 Gridded geometry for a canvas 44.2 Telling other applications what your name is 45. Managing User Preferences 45.1 Preferences initialization 45.2 Adding preference items 45.3 Setting preference variables 45.4 Using the preferences package 45.5 A user interface to the preference items 45.6 Interface objects for different preference types 45.7 Displaying the help text for an item 45.8 Saving preferences settings to a file 45.9 Read settings from the preferences file 45.10 Tracing a Tcl variable in a preference item 46. A User Interface to Bindings This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks 46.1 A user interface to widget bindings 46.2 Bind_Display presents the bindings for a widget or class 46.3 Related listboxes are configured to select items together 46.4 Controlling a pair of listboxes with one scrollbar 46.5 Drag-scrolling a pair of listboxes together 46.6 An interface to define bindings 46.7 Defining and saving bindings 47. C Programming and Tcl 47.1 The initialization procedure for a loadable package 47.2 The RandomCmd C command procedure 47.3 The RandomObjCmd C command procedure 47.4 The Tcl_Obj structure 47.5 The Plus1ObjCmd procedure 47.6 The Blob and BlobState data structures 47.7 The Blob_Init and BlobCleanup procedures 47.8 The BlobCmd command procedure 47.9 BlobCreate and BlobDelete 47.10 The BlobNames procedure 47.11 \The BlobN and BlobData procedures 47.12 The BlobCommand and BlobPoke procedures 47.13 A canonical Tcl main program and Tcl_AppInit 47.14 A canonical Tk main program and Tk_AppInit 47.15 Calling C command procedure directly with Tcl_Invoke 48. Compiling Tcl and Extensions This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Writing a Tk Widget in C 49.1 The Clock_Init procedure 49.2 The Clock widget data structure 49.3 The ClockCmd command procedure 49.4 The ClockObjCmd command procedure 49.5 The ClockInstanceCmd command procedure 49.6 The ClockInstanceObjCmd command procedure 49.7 ClockConfigure allocates resources for the widget 49.8 ClockObjConfigure allocates resources for the widget 49.9 The Tk_ConfigSpec typedef 49.10 Configuration specs for the clock widget 49.11 The Tk_OptionSpec typedef 49.12 The Tk_OptionSpec structure for the clock widget 49.13 ComputeGeometry computes the widget's size 49.14 The ClockDisplay procedure 49.15 The ClockEventProc handles window events 49.16 The ClockDestroy cleanup procedure 49.17 The ClockObjDelete command 500C Library Overview [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] List of Tables 1. Tcl Fundamentals 1-1 Backslash sequences 1-2 Arithmetic operators from highest to lowest precedence 1-3 Built-in math functions 1-4 Built-in Tcl commands 2. Getting Started 2-1 Wish command line options 2-2 Variables defined by tclsh and wish 3. The Guestbook CGI Application 3-1 HTML tags used in the examples 4. String Processing in Tcl 4-1 The string command 4-2 Matching characters used with string match 4-3 Character class names 4-4 Format conversions 4-5 Format flags 4-6 Binary conversion types 5. Tcl Lists 5-1 List-related commands 5-2 Options to the lsearch command This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks 8. Tcl Arrays 8-1 The array command 9. Working with Files and Programs 9-1 Summary of the exec syntax for I/O redirection 9-2 The file command options 9-3 Array elements defined by file stat 9-4 Platform-specific file attributes 9-5 Tcl commands used for file access 9-6 Summary of the open access arguments 9-7 Summary of POSIX flags for the access argument 9-8 glob command options 9-9 The registry command 9-10 The registry data types 11. Regular Expressions 11-1 Additional advanced regular expression syntax 11-2 Backslash escapes in regular expressions 11-3 Character classes 11-4 Embedded option characters used with the (?x) syntax 11-5 Options to the regexp command 11-6 Sample regular expressions 12. Script Libraries and Packages 12-1 Options to the pkg_mkIndex command 12-2 The package command 13. Reflection and Debugging This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 13-1 clock format keywords 13-2 The clock command 13-3 The info command 13-4 The history command 13-5 Special history syntax 14. Namespaces 14-1 The namespace command 15. Internationalization 15-1 The encoding command 15-2 The msgcat package 16. Event-Driven Programming 16-1 The after command 16-2 The fileevent command 16-3 I/O channel properties controlled by fconfigure 16-4 Serial line properties controlled by fconfigure 16-5 End of line translation modes 17. Socket Programming 17-1 Options to the http::geturl command 17-2 The http support procedures 17-3 Elements of the http::geturl state array 18. TclHttpd Web Server 18-1 Httpd support procedures 18-2 Url support procedures 18-3 Doc procedures for configuration This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 18-4 Doc procedures for generating responses 18-5 Doc procedures that support template processing 18-6 Elements of the page array 18-7 Elements of the env array 18-8 Status Application Direct URLs 18-9 Debug Application Direct URLs 18-10 Application Direct URLS that email form results 18-11 Basic TclHttpd parameters 19. Multiple Interpreters and Safe-Tcl 19-1 The interp command 19-2 Commands hidden from safe interpreters 19-3 The safe base master interface 19-4 The safe base slave aliases 20. Safe-Tk and the Browser Plugin 20-1 Tk commands omitted from safe interpreters 20-2 Aliases defined by the browser package 20-3 The browser::getURL callbacks 21. Multi-Threaded Tcl Scripts 21-1 The commands of the thread namespace 21-2 Thread configuration options 21-3 The commands of the tsv namespace 21-4 The commands of the tpool namespace 21-5 Thread pool configuration options 22. Tclkit and Starkits 22-1 Return values of the starkit::startup procedure This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 23. Tk Fundamentals 23-1 Tk widget-creation commands 23-2 Tk widget-manipulation commands 23-3 Tk support procedures 25. The Pack Geometry Manager 25-1 The pack command 25-2 Packing options 26. The Grid Geometry Manager 26-1 The grid command 26-2 Grid widget options 27. The Place Geometry Manager 27-1 The place command 27-2 Placement options 28. The Panedwindow Widget 28-1 Panedwindow operations 28-2 Panedwindow attributes 28-3 Panedwindow managed widget options 29. Binding Commands to Events 29-1 Event types 29-2 Event modifiers 29-3 The event command 29-4 A summary of the event keywords 30. Buttons and Menus 30-1 Resource names of attributes for all button widgets 30-2 Button operations This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 30-3 Menu entry index keywords 30-4 Menu operations 30-5 Menu attribute resource names 30-6 Attributes for menu entries 32. Simple Tk Widgets 32-1 Attributes for frame, labelframe, and toplevel widgets 32-2 Label Attributes 32-3 Message Attributes 32-4 Bindings for scale widgets 32-5 Attributes for scale widgets 32-6 Operations on the scale widget 33. Scrollbars 33-1 Attributes for the scrollbar widget 33-2 Bindings for the scrollbar widget 33-3 Operations on the scrollbar widget 34. The Entry and Spinbox Widgets 34-1 Entry and spinbox validation substitutions 34-2 Entry and spinbox bindings 34-3 Entry and spinbox attribute resource names 34-4 Entry and spinbox indices 34-5 Entry and spinbox operations 35. The Listbox Widget 35-1 Listbox indices 35-2 Listbox operations 35-3 Listbox item configuration options This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 35-4 The values for the selectMode of a listbox 35-5 Bindings for browse selection mode 35-6 Bindings for single selection mode 35-7 Bindings for extended selection mode 35-8 Bindings for multiple selection mode 35-9 Listbox scroll bindings 35-10 Listbox attribute resource names 36. The Text Widget 36-1 Text indices 36-2 Index modifiers for text widgets 36-3 Attributes for text tags 36-4 Options to the search operation 36-5 Window and image alignment options 36-6 Options to the window create operation 36-7 Options to the image create operation 36-8 Bindings for the text widget 36-9 Operations for the text widget 36-10 Text attribute resource names 37. The Canvas Widget 37-1 Common canvas item attributes 37-2 Canvas dash pattern characters 37-3 Arc attributes 37-4 Bitmap attributes 37-5 Image attributes 37-6 Line attributes This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 37-7 Polygon attributes 37-8 Indices for canvas text items 37-9 Canvas operations that apply to text items 37-10 Text attributes 37-11 Window attributes 37-12 Operations on a canvas widget 37-13 Canvas postscript options 37-14 Canvas attribute resource names 38. Selections and the Clipboard 38-1 The selection command 38-2 The clipboard command 39. Focus, Grabs, and Dialogs 39-1 Options to tk_messageBox 39-2 Options to the standard file and directory dialogs 39-3 Options to tk_chooseColor 39-4 The focus command 39-5 The grab command 39-6 he tkwait command 40. Tk Widget Attributes 40-1 Size attribute resource names 40-2 Border and relief attribute resource names 40-3 Highlight attribute resource names 40-4 Layout attribute resource names 41. Color, Images, and Cursors 41-1 Color attribute resource names This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. 41-2 Windows system colors 41-3 Macintosh system colors 41-4 Visual classes for displays 41-5 Summary of the image command 41-6 Bitmap image options 41-7 Photo image attributes 41-8 Photo image operations 41-9 Copy options for photo images 41-10 Read options for photo images 41-11 Write options for photo images 41-12 Cursor attribute resource names 42. Fonts and Text Attributes 42-1 Font attributes 42-2 X Font specification components 42-3 Layout attribute resource names 42-4 The font command 42-5 Selection attribute resource names 43. Send 43-1 Options to the send command 44. Window Managers and Window Information 44-1 Size, placement and decoration window manager operations 44-2 Window manager commands for icons 44-3 Session-related window manager operations 44-4 Miscellaneous window manager operations 44-5 send command information This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks 44-6 Window hierarchy information 44-7 Window location information 44-8 Window size information 44-9 Virtual root window information 44-10 Atom and window ID information 44-11 Colormap and visual class information 44-12 The tk command operations 47. C Programming and Tcl 47-1 Defines to control the meaning of CONST in the Tcl APIs 48. Compiling Tcl and Extensions 48-1 The Tcl source directory structure 48-2 The installation directory structure 48-3 Standard configure flags 48-4 TEA standard Makefile targets 49. Writing a Tk Widget in C 49-1 Configuration flags and corresponding C types 51. Tcl 7.4/Tk 4.0 51-1 Changes in color attribute names 55. Tcl/Tk 8.1 55-1 The testthread command 55-2 The dde command options [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Preface Tcl stands for Tool Command Language. Tcl is really two things: a scripting language, and an interpreter for that language that is designed to be easy to embed into your application. Tcl and its associated graphical user-interface toolkit, Tk, were designed and crafted by Professor John Ousterhout of the University of California, Berkeley. You can find these packages on the Internet and use them freely in your application, even if it is commercial. The Tcl interpreter has been ported from UNIX to DOS, PalmOS, VMS, Windows, OS/2, NT, and Macintosh environments. The Tk toolkit has been ported from the X window system to Windows and Macintosh. I first heard about Tcl in 1988 while I was Ousterhout's Ph.D. student at Berkeley. We were designing a network operating system, Sprite. While the students hacked on a new kernel, John wrote a new editor and terminal emulator. He used Tcl as the command language for both tools so that users could define menus and otherwise customize those programs. This was in the days of X10, and he had plans for an X toolkit based on Tcl that would help programs cooperate with each other by communicating with Tcl commands. To me, this cooperation among tools was the essence of Tcl. This early vision imagined that applications would be large bodies of compiled code and a small amount of Tcl used for configuration and high-level commands. John's editor, mx, and the terminal emulator,tx, followed this model. While this model remains valid, it has also turned out to be possible to write entire applications in Tcl. This is because the Tcl/Tk shell, wish, provides access to other programs, the file system, network sockets, plus the ability to create a graphical user interface. For better or worse, it is now common to find applications that contain thousands of lines of Tcl script. This book was written because, while I found it enjoyable and productive to use Tcl and Tk, there were times when I was frustrated. In addition, working at Xerox PARC, with many experts in languages and systems, I was compelled to understand both the strengths and weaknesses of Tcl and Tk. Although many of my colleagues adopted Tcl and Tk for their projects, they were also just as quick to point out its flaws. In response, I have built up a set of programming techniques that exploit the power of Tcl and Tk while avoiding troublesome areas. This book is meant as a practical guide to help you get the most out of Tcl and Tk and avoid some of the frustrations I experienced. It has been about 14 years since I was introduced to Tcl, and about eight years since the first edition of this book. During several of those years I worked under John Ousterhout, first at Sun Microsystems and then at Scriptics Corporation. There I remained mostly a Tcl programmer while others in our group have delved into the C implementation of Tcl itself. I've built applications like HTML editors, email user interfaces, Web servers, and the customer database we ran our business on. This experience is reflected in this book. The bulk of the book is about Tcl scripting, and the aspects of C programming to create Tcl extensions is given a lighter treatment. I have been lucky to remain involved in the core Tcl development, and I hope I can pass along the insights I have gained by working with Tcl. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Why Tcl? As a scripting language, Tcl is similar to other UNIX shell languages such as the Bourne Shell (sh), the C Shell (csh), the Korn Shell (ksh), and Perl. Shell programs let you execute other programs. They provide enough programmability (variables, control flow, and procedures) to let you build complex scripts that assemble existing programs into a new tool tailored for your needs. Shells are wonderful for automating routine chores. It is the ability to easily add a Tcl interpreter to your application that sets it apart from other shells. Tcl fills the role of an extension language that is used to configure and customize applications. There is no need to invent a configuration file format or a command language for your new application, or struggle to provide some sort of user-programmability for your tool. Instead, by adding a Tcl interpreter, you structure your application as a set of primitive operations that can be composed by a script to best suit the needs of your users. It also allows other programs to have programmatic control over your application, leading to suites of applications that work well together. The Tcl C library has clean interfaces and is simple to use. The library implements the basic interpreter and a set of core scripting commands that implement variables, flow control, and procedures (see page 22). There is also a broad set of APIs that access operating system services to run other programs, access the file system, and use network sockets. Tk adds commands to create graphical user interfaces. The Tcl and Tk C APIs provide a "virtual machine" that is portable across UNIX, Windows, and Macintosh environments. The Tcl virtual machine is extensible because your application can define new Tcl commands. These commands are associated with a C or C++ procedure that your application provides. The result is applications that are split into a set of primitives written in a compiled language and exported as Tcl commands. A Tcl script is used to compose the primitives into the overall application. The script layer has access to shell-like capability to run other programs, has access to the file system, and can call directly into the compiled part of the application through the Tcl commands you define. In addition, from the C programming level, you can call Tcl scripts, set and query Tcl variables, and even trace the execution of the Tcl interpreter. There are many Tcl extensions freely available on the Internet. Most extensions include a C library that provides some new functionality, and a Tcl interface to the library. Examples include database access, telephone control, MIDI controller access, and expect, which adds Tcl commands to control interactive programs. The most notable extension is Tk, a toolkit for graphical user interfaces. Tk defines Tcl commands that let you create and manipulate user interface widgets. The script-based approach to user interface programming has three benefits: Development is fast because of the rapid turnaround; there is no waiting for long compilations. The Tcl commands provide a higher-level interface than most standard C library user-interface toolkits. Simple user interfaces require just a handful of commands to define them. At the same time, it is possible to refine the user interface in order to get every detail just so. The fast turnaround aids the refinement process. The user interface can be factored out from the rest of your application. The developer can concentrate on the implementation of the application core and then fairly painlessly work up a user interface. The core set of Tk widgets is often sufficient for all your user interface needs. However, it is also possible to write custom Tk widgets in C, and again there are many contributed Tk widgets available on the network. There are other choices for extension languages that include Visual Basic, Scheme, Elisp, Perl, Python, Ruby and Javascript. Your choice between them is partly a matter of taste. Tcl has simple constructs and looks somewhat like C. It is easy to add new Tcl primitives by writing C procedures. Tcl is very easy to learn, and I have heard many great stories of users completing impressive projects in a short amount of time (e.g., a few weeks), even though they never used Tcl before. Java has exploded onto the computer scene since this book was first published. Java is a great systems programming language that in the long run could displace C and C++. This is fine for Tcl, which is designed to glue together building blocks written in any system programming This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks language. Tcl was designed to work with C, but has been adapted to work with the Java Virtual Machine. Where I say "C or C++", you can now say "C, C++, or Java," but the details are a bit different with Java. This book does not describe the Tcl/Java interface, but you can find TclBlend on the CD-ROM. TclBlend loads the Java Virtual Machine into your Tcl application and lets you invoke Java methods. It also lets you implement Tcl commands in Java instead of C or C++. Jacl is a Tcl interpreter written in Java. It has some limitations compared with the native C-based Tcl interpreter, but Jacl is great if you cannot use the native interpreter. Javascript is a language from Netscape that is designed to script interactions with Web pages. Javascript is important because of its use in HTML user interfaces. However, Tcl provides a more general purpose scripting solution that can be used in a wide variety of applications. The Tcl/Tk Web browser plugin provides a way to run Tcl in your browser. It turns out to be more of a Java alternative than a JavaScript alternative. The plugin lets you run Tcl applications inside your browser, while JavaScript gives you fine grain control over the browser and HTML display. The plugin is described in Chapter 20. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Tcl and Tk Versions Tcl and Tk continue to evolve. See http://www.beedub.com/book/ for updates and news about the latest Tcl releases. Tcl and Tk have had separate version numbers for historical reasons, but they are released in pairs that work together. The original edition of this book was based on Tcl 7.4 and Tk 4.0, and there were a few references to features in Tk 3.6. This fourth edition has been updated to reflect new features added through Tcl/Tk 8.4: Tcl 7.5 and Tk 4.1 had their final release in May 1996. These releases feature the port of Tk to the Windows and Macintosh environments. The Safe-Tcl security mechanism was introduced to support safe execution of network applets. There is also network socket support and a new Input/Output (I/O) subsystem to support high-performance event-driven I/O. Tcl 7.6 and Tk 4.2 had their final release in October 1996. These releases include improvements in Safe-Tcl, and improvements to the grid geometry manager introduced in Tk 4.1. Cross-platform support includes virtual events (e.g., <<Copy>> as opposed to <Control-c>), standard dialogs, and more file manipulation commands. Tcl 7.7 and Tk 4.3 were internal releases used for the development of the Tcl/Tk plug-in for the Netscape Navigator and Microsoft Internet Explorer Web browsers. Their development actually proceeded in parallel to Tcl 7.6 and Tk 4.2. The plug-in has been released for a wide variety of platforms, including Solaris/SPARC, Solaris/INTEL, SunOS, Linux, Digital UNIX, IRIX, HP/UX, Windows 95, Windows NT, and the Macintosh. The browser plug-in supports Tcl applets in Web pages and uses the sophisticated security mechanism of Safe-Tcl to provide safety. Tcl 8.0 features an on-the-fly compiler for Tcl that provides many-times faster Tcl scripts. Tcl 8.0 supports strings with embedded null characters. The compiler is transparent to Tcl scripts, but extension writers need to learn some new C APIs to take advantage of its potential. The release history of 8.0 spread out over a couple of years as John Ousterhout moved from Sun Microsystems to Scriptics Corporation. The widely used 8.0p2 release was made in the fall of 1997, but the final patch release, 8.0.5, was made in the spring of 1999. Tk changed its version to match Tcl at 8.0. Tk 8.0 includes a new platform-independent font mechanism, native menus and menu bars, and more native widgets for better native look and feel on Windows and Macintosh. Tcl/Tk 8.1 features full Unicode support, a new regular expression engine that provides all the features found in Perl 5, and thread safety so that you can embed Tcl into multi threaded applications. Tk does a heroic job of finding the correct font to display your Unicode characters, and it adds a message catalog facility so that you can write internationalized applications. The release history of Tcl/Tk 8.1 also straddled the Sun to Scriptics transition. The first alpha release was made in the fall of 1997, and the final patch release, 8.1.1, was made in May 1999. Tcl/Tk 8.2 is primarily a bug fix and stabilization release. There are a few minor additions to the Tcl C library APIs to support more extensions without requiring core patches. Tcl/Tk 8.2 went rapidly into final release in the summer of 1999. Tcl/Tk 8.3 adds a broad collection of enhancements to Tcl and Tk. Tk started to get some long deserved attention with adoption of the Dash and Image patches from Jan Nijtmans. The 8.3.0 release was in February, 2000, and the last patch release, 8.3.5, was made in October, 2002. Tcl/Tk 8.4 features a focus on performance, the addition of the Virtual File System Interface, and 3 new core Tk widgets: spinbox, labeledframe, and panedwindow. This release was a long time in development. The first beta release was in June, 2000, and the 8.4.2 release was made in March, 2003. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Extending Tcl and Tk Tcl is designed so that interesting additions can be made as extensions that do not require changes to the Tcl core. Many extensions are available today: You can find them on the Web at: http://www.tcl.tk/resource/ However, some changes require changes to Tcl/Tk itself. If you are interested in contributing to the continued improvement of Tcl/Tk, you can help. There is a Tcl Core Team (TCT) and a formal Tcl Improvement Process (TIP). You can browse the current TIPs or contribute your own at: http://www.tcl.tk/cgi-bin/tct/tip/ The Tcl and Tk source code is maintained on a SourceForge project: http://www.sourceforge.net/projects/tcl http://www.sourceforge.net/projects/tktoolkit All bug reports and patch submissions are logged in a database. Source code patches that are made according to the Tcl Engineering Manual guidelines have the most chance of adoption. These guidelines describe code appearance (e.g., indentation), test suite requirements, and documentation requirements. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Tcl on the World Wide Web Start with these World Wide Web pages about Tcl: http://www.tcl.tk/ http://tcl.activestate.com/ http://www.purl.org/NET/Tcl-FAQ/ The Tcler's Wiki is a very active site that is updated by its users (i.e., by you) with lots of great information about Tcl and its extensions: http://wiki.tcl.tk/ The home page for this book contains errata for all editions. This is the only URL I control personally, and I plan to keep it up-to-date indefinitely: http://www.beedub.com/book/ The Prentice Hall Web site is: http://www.prenhall.com/ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Ftp Archives These are some of the FTP sites that maintain Tcl archives: ftp://ftp.tcl.tk/pub/tcl ftp://src.doc.ic.ac.uk/packages/tcl/ ftp://ftp.luth.se/pub/unix/tcl/ ftp://ftp.sunet.se/pub/lang/tcl ftp://ftp.cs.columbia.edu/archives/tcl ftp://ftp.funet.fi/pub/languages/tcl You can use a World Wide Web browser likeMozilla, Netscape, Internet Explorer, or Lynx to access these sites. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Newsgroups The comp.lang.tcl newsgroup is very active. It provides a forum for questions and answers about Tcl. Announcements about Tcl extensions and applications are posted to the comp.lang.tcl.announce newsgroup. The following web service provides a convenient way to read newsgroups. Enter comp.lang.tcl in the search field on this page: http://groups.google.com [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Who Should Read This Book This book is meant to be useful to the beginner in Tcl as well as the expert. For the beginner and expert alike, I recommend careful study of Chapter 1, Tcl Fundamentals. The programming model of Tcl is designed to be simple, but it is different from many programming languages. The model is based on string substitutions, and it is important that you understand it properly to avoid trouble in complex cases. The remainder of the book consists of examples that demonstrate how to use Tcl and Tk productively. For your reference, each chapter has tables that summarize the Tcl commands and Tk widgets they describe. This book assumes that you have some programming experience, although you should be able to get by even if you are a complete novice. Knowledge of UNIX shell programming will help, but it is not required. Where aspects of window systems are relevant, I provide some background information. Chapter 2 describes the details of using Tcl and Tk on UNIX, Windows, and Macintosh. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] How to Read This Book This book is best used in a hands-on manner, trying the examples at the computer. The book tries to fill the gap between the terse Tcl and Tk manual pages, which are complete but lack context and examples, and existing Tcl programs that may or may not be documented or well written. I recommend the on-line manual pages for the Tcl and Tk commands. They provide a detailed reference guide to each command. This book summarizes much of the information from the manual pages, but it does not provide the complete details, which can vary from release to release. HTML versions of the on-line manual pages can be found on the CD-ROM that comes with this book. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] On-line Examples The book comes with a CD-ROM that has source code for all of the examples, plus a selection of Tcl freeware found on the Internet. The CD-ROM is readable on UNIX, Windows, and Macintosh. There, you will find the versions of Tcl and Tk that were available as the book went to press. You can also retrieve the sources shown in the book from my personal Web site: http://www.beedub.com/book/ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Typographic Conventions The more important examples are set apart with a title and horizontal rules, while others appear in-line. The examples use courier for Tcl and C code. When interesting results are returned by a Tcl command, those are presented below in oblique courier. The => is not part of the return value in the following example. expr 5 + 8 => 13 The courier font is also used when naming Tcl commands and C procedures within sentences. The usage of a Tcl command is presented in the following example. The command name and constant keywords appear in courier. Variable values appear in courier oblique. Optional arguments are surrounded with question marks. set varname ?value? The name of a program is in italics: xterm [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Hot Tips The icon in the margin marks a "hot tip" as judged by the reviewers of the book. The visual markers help you locate the more useful sections in the book. These are also listed in the index under Hot Tip. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Book Organization The chapters of the book are divided into seven parts. The first part describes basic Tcl features. The first chapter describes the fundamental mechanisms that characterize the Tcl language. This is an important chapter that provides the basic grounding you will need to use Tcl effectively. Even if you have programmed in Tcl already, you should review Chapter 1. Chapter 2 goes over the details of using Tcl and Tk on UNIX, Windows, and Macintosh. Chapter 3 presents a sample application, a CGI script, that illustrates typical Tcl programming. The rest of Part I covers the basic Tcl commands in more detail, including string handling, data types, control flow, procedures, and scoping issues. Part I finishes with a description of the facilities for file I/O and running other programs. Part II describes advanced Tcl programming. It starts witheval, which lets you generate Tcl programs on the fly. Regular expressions provide powerful string processing. If your data-processing application runs slowly, you can probably boost its performance significantly with the regular expression facilities. Namespaces partition the global scope of procedures and variables. Unicode and message catalogs support internationalized applications. Libraries and packages provide a way to organize your code for sharing among projects. The introspection facilities of Tcl tell you about the internal state of Tcl. Event driven I/O helps server applications manage several clients simultaneously. Network sockets are used to implement the HTTP protocol used to fetch pages on the World Wide Web. The last few chapters in Part II describe platforms and frameworks for application development. Safe-Tcl is used to provide a secure environment to execute Tcl applets in a Web browser. TclHttpd is an extensible web server built in Tcl. You can build applications on top of this server, or embed it into your existing applications to give them a web interface. Starkits are an exciting new way to package and deploy Tcl/Tk applications. They use the new Virtual File System (VFS) facilities to embed a private file system right in the Starkit. Part III introduces Tk. It gives an overview of the toolkit facilities. A few complete examples are examined in detail to illustrate the features of Tk. Event bindings associate Tcl commands with events like keystrokes and button clicks. Part III ends with three chapters on the Tk geometry managers that provide powerful facilities for organizing your user interface. Part IV describes the Tk widgets. These include buttons, menus, scrollbars, labels, text entries, multiline and multifont text areas, drawing canvases, listboxes, and scales. The Tk widgets are highly configurable and very programmable, but their default behaviors make them easy to use as well. The resource database that can configure widgets provides an easy way to control the overall look of your application. Part V describes the rest of the Tk facilities. These include selections, keyboard focus, and standard dialogs. Fonts, colors, images, and other attributes that are common to the Tk widgets are described in detail. This part ends with a few larger Tk examples. Part VI is an introduction to C programming and Tcl. The goal of this part is to get you started in the right direction when you need to extend Tcl with new commands written in C or integrate Tcl into custom applications. Part VII provides a chapter for each of the Tcl/Tk releases covered by the book. These chapters provide details about what features were changed and added. They also provide a quick reference if you need to update a program or start to use a new version. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] What's New in the Fourth Edition The fourth edition is up-to-date with Tcl/Tk 8.4, which adds many new features. Tcl has a new Virtual File System (VFS) feature that lets you transparently embed a file system in your application, or make remote resources such as FTP and Web sites visible through the regular file system interface. Chapter 22 is a new chapter on Tclkit and Starkits that use the Metakit embedded database to store scripts and other files. The VFS makes these files appear in a private file system. Starkits provide a new way to package and deploy Tcl/Tk applications. Chapter 21 is a new chapter on using the multi-threading support in Tcl. This is very useful when embedding Tcl in threaded server applications. There are a number of new Tk features, including three new widgets. The spinbox (i.e., combobox) is like an entry widget with a drop-down selection box. The labeled frame provides a new way to decorate frames. The panedwindow is a specialized geometry manager that provides a new way to organize your user interfaces. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks . [ Team LiB ] Other Tcl Books This book was the second Tcl book after the original book by John Ousterhout, the creator of Tcl. Since then, many other Tcl books have been published. The following are just some of the books currently available. Tcl/Tk: A Developer's Guide, 2nd Ed. (Academic Press, 2003) by Clif Flynt is a good example-oriented book that has been recently updated. Tcl and the Tk Toolkit (Addison-Wesley, 1994) by John Ousterhout provides a broad overview of all aspects of Tcl and Tk, even though it covers only Tcl 7.3 and Tk 3.6. The book provides a more detailed treatment of C programming for Tcl extensions. Exploring Expect (O'Reilly & Associates, Inc., 1995) by Don Libes is a great book about an extremely useful Tcl extension.Expect lets you automate the use of interactive programs like ftp and telnet that expect to interact with a user. By combiningExpect and Tk, you can create graphical user interfaces for old applications that you cannot modify directly. Tcl/Tk in a Nutshell (O'Reilly, 1999) by Paul Raines and Jeff Tranter is a handy reference guide. It covers several popular extensions including Expect, [incr Tcl], Tix, TclX, BLT, SybTcl, OraTcl, and TclODBC. There is a tiny pocket-reference guide for Tcl/Tk that may eliminate the need to thumb through my large book to find the syntax of a particular Tcl or Tk command. Web Tcl Complete (McGraw Hill, 1999) by Steve Ball describes programming with the Tcl Web Server. It also covers Tcl/Java integration using TclBlend. [incr Tcl] From The Ground Up (Osborn-McGraw Hill, 1999) by Chad Smith describes the [incr Tcl] object-oriented extension to Tcl. Tcl/Tk for Programmers (IEEE Computer Society, 1998) by Adrian Zimmer describes Unix and Windows programming with Tcl/Tk. This book also includes solved exercises at the end of each chapter. Building Network Management Tools with Tcl/Tk (Prentice Hall, 1998) by Dave Zeltserman and Gerald Puoplo. This describes how to build SNMP tools. Graphical Applications with Tcl & Tk (M&T Books, 1997) by Eric Johnson is oriented toward Windows users. The second edition covers Tcl/Tk 8.0. Tcl/Tk Tools (O'Reilly & Associates, Inc., 1997) by Mark Harrison describes many useful Tcl extensions. These include Oracle and Sybase interfaces, object-oriented language enhancements, additional Tk widgets, and much more. The chapters were contributed by the authors of the extensions, so they provide authoritative information on some excellent additions to the Tcl toolbox. Effective Tcl/Tk Programming (Addison Wesley, 1997) by Michael McLennan and Mark Harrison illustrate Tcl and Tk with examples and application design guidelines. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] First Edition Thanks I would like to thank my managers and colleagues at Xerox PARC for their patience with me as I worked on this book. The tips and tricks in this book came partly from my own work as I helped lab members use Tcl, and partly from them as they taught me. Dave Nichols' probing questions forced me to understand the basic mechanisms of the Tcl interpreter. Dan Swinehart and Lawrence Butcher kept me sharp with their own critiques. Ron Frederick and Berry Kerchival adopted Tk for their graphical interfaces and amazed me with their rapid results. Becky Burwell, Rich Gold, Carl Hauser, John Maxwell, Ken Pier, Marvin Theimer, and Mohan Vishwanath made use of my early drafts, and their questions pointed out large holes in the text. Karin Petersen, Bill Schilit, and Terri Watson kept life interesting by using Tcl in very nonstandard ways. I especially thank my managers, Mark Weiser and Doug Terry, for their understanding and support. I thank John Ousterhout for Tcl and Tk, which are wonderful systems built with excellent craftsmanship. John was kind enough to provide me with an advance version of Tk 4.0 so that I could learn about its new features well before its first beta release. Thanks to the Tcl programmers out on the Net, from whom I learned many tricks. John LoVerso and Stephen Uhler are the hottest Tcl programmers I know. Many thanks to the patient reviewers of early drafts: Pierre David, Clif Flynt, Simon Kenyon, Eugene Lee, Don Libes, Lee Moore, Joe Moss, Hador Shemtov, Frank Stajano, Charles Thayer, and Jim Thornton. Many folks contributed suggestions by email: Miguel Angel, Stephen Bensen, Jeff Blaine, Tom Charnock, Brian Cooper, Patrick D'Cruze, Benoit Desrosiers, Ted Dunning, Mark Eichin, Paul Friberg, Carl Gauthier, David Gerdes, Klaus Hackenberg, Torkle Hasle, Marti Hearst, Jean-Pierre Herbert, Jamie Honan, Norman Klein, Joe Konstan, Susan Larson, Håkan Liljegren, Lionel Mallet, Dejan Milojicic, Greg Minshall, Bernd Mohr, Will Morse, Heiko Nardmann, Gerd Neugebauer, TV Raman, Cary Renzema, Rob Riepel, Dan Schenk, Jean-Guy Schneider, Elizabeth Scholl, Karl Schwamb, Rony Shapiro, Peter Simanyi, Vince Skahan, Bill Stumbo, Glen Vanderburg, Larry Virden, Reed Wade, and Jim Wight. Unfortunately, I could not respond to every suggestion, even some that were excellent. Thanks to the editors and staff at Prentice Hall. Mark Taub has been very helpful as I progressed through my first book. Lynn Schneider and Kerry Reardon were excellent copy and production editors, respectively. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Second Edition Thanks I get to thank John Ousterhout again, this time for supporting me as I worked in the Tcl/Tk group at Sun Microsystems. The rest of the group deserve a lot of credit for turning Tcl and Tk into a dynamite cross-platform solution. Scott Stanton led the Tk port to the PC. Ray Johnson led the Tk port to the Macintosh. Jacob Levy implemented the event-driven I/O system, Safe-Tcl, and the browser plug-in. Brian Lewis built the Tcl compiler. Ken Corey worked on Java integration and helped with the SpecTcl user interface builder. Syd Polk generalized the menu system to work with native widgets on the Macintosh and Windows. Colin Stevens generalized the font mechanism and worked on internationalization for Tk. Stephen Uhler deserves special thanks for inspiring many of the cool examples I use in this book. He was the lead on the SpecTcl user interface builder. He built the core HTML display library on which I based an editor. We worked closely together on the first versions of TclHttpd. He taught me how to write compact, efficient Tcl code and to use regular expression substitutions in amazing ways. I hope he has learned at least a little from me. Thanks again to Mark Taub, Eileen Clark, and Martha Williams at Prentice Hall. George Williams helped me assemble the files for the CD-ROM. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Third Edition Thanks John Ousterhout continues his wonderful role as Tcl benefactor, now as founder of Scriptics Corporation. I'd like to thank every one of the great folks that I work with at Scriptics, especially the pioneering crew of Sarah Daniels, Scott Stanton, Ray Johnson, Bryan Surles, Melissa Chawla, Lee Bernhard, Suresh Sastry, Emil Scaffon, Pat P., Scott Redman, and Berry Kercheval. The rest of the gang deserves a big thanks for making Scriptics such an enjoyable place to work. Jerry Peek, who is a notable author himself, provided valuable advice and wonderfully detailed comments! Ken Jones told me about a great indexing tool. I'd like to thank all the readers that drop me the encouraging note or probing question via email. I am always interested in new and interesting uses of Tcl! Thanks to the editors at Prentice Hall: Mark Taub, Joan McNamara, and Joan Eurell. Mark continues to encourage me to come out with new editions, and the Joans helped me complete this third edition on time. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Fourth Edition Thanks I'd like to thank Jeff Hobbs and Ken Jones for making this project happen. Jeff has done a great service to the Tcl community as "The Tcl Guy". His leadership and hard work have been responsible for the steady pace of new Tcl/Tk releases. Ken is a great Tcl teacher and his experiences teaching are reflected in his additions to the book for this 4th edition. Again, without these two lending a hand, I just wouldn't have found the time for this edition. I'd like to thank the Tcl Core Team and the supporting cast of contributors to the Tcl/Tk code base. The TCT provides a great framework to keep Tcl a high quality software product that continues to adopt new an interesting features. I'd like to thank Jean-Claude Wippler and Steve Landers for Metakit, Tclkit, and Starkits. These provide a delightful way to package and deploy Tcl applications. I expect to see a lot more from these technologies in the future. Several readers provided great feedback on the Starkit material: Robert Techentin, Steve Blinkhorn, Frank Sergeant, Arjen Markus, Uwe Koloska, Larry Virden, Tom Krehbiel, and Donald Porter. I'd like to thank Prentice Hall for their continued support. Mark Taub continues his role as godfather of this book. Kathleen Caren was the able production editor for this edition. Finally, I thank my wonderful wife Jody for her love, kindness, patience, wit, and understanding as I worked long hours. Happily, many of those hours were spent working from home. I now have three sons, Christopher, Daniel, and Michael, who get the credit for keeping me from degenerating into a complete nerd. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Contact the Author I am always open to comments about this book. My email address is welch@acm.org. It helps me sort through my mail if you put the word "book" or the title of the book into the email subject line. Visit my Web site for current news about the book and my other interests. I maintain an errata page for each edition, so please consult that and feel free to send bug reports! http://www.beedub.com/ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Part I: Tcl Basics Part I introduces the basics of Tcl. Everyone should read Chapter 1, which describes the fundamental properties of the language. Tcl is really quite simple, so beginners can pick it up quickly. The experienced programmer should review Chapter 1 to eliminate any misconceptions that come from using other languages. Chapter 2 is a short introduction to running Tcl and Tk on UNIX, Windows, and Macintosh systems. You may want to look at this chapter first so you can try out the examples as you read Chapter 1. Chapter 3 presents a sample application, a CGI script, that implements a guestbook for a Web site. The example uses several facilities that are described in detail in later chapters. The goal is to provide a working example that illustrates the power of Tcl. The rest of Part I covers basic programming with Tcl. Simple string processing is covered inChapter 4. Tcl lists, which share the syntax rules of Tcl commands, are explained in Chapter 5. Control structure like loops and if statements are described in Chapter 6. Chapter 7 describes Tcl procedures, which are new commands that you write in Tcl.Chapter 8 discusses Tcl arrays. Arrays are the most flexible and useful data structure in Tcl. Chapter 9 describes file I/O and running other programs. These facilities let you build Tcl scripts that glue together other programs and process data in files. After reading Part I you will know enough Tcl to read and understand other Tcl programs, and to write simple programs yourself. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 1. Tcl Fundamentals This chapter describes the basic syntax rules for the Tcl scripting language. It describes the basic mechanisms used by the Tcl interpreter: substitution and grouping. It touches lightly on the following Tcl commands: puts, format, set, expr, string, while, incr, and proc. Tcl is a string-based command language. The language has only a few fundamental constructs and relatively little syntax, which makes it easy to learn. The Tcl syntax is meant to be simple. Tcl is designed to be a glue that assembles software building blocks into applications. A simpler glue makes the job easier. In addition, Tcl is interpreted when the application runs. The interpreter makes it easy to build and refine your application in an interactive manner. A great way to learn Tcl is to try out commands interactively. If you are not sure how to run Tcl on your system, see Chapter 2 for instructions for starting Tcl on UNIX, Windows, and Macintosh systems. This chapter takes you through the basics of the Tcl language syntax. Even if you are an expert programmer, it is worth taking the time to read these few pages to make sure you understand the fundamentals of Tcl. The basic mechanisms are all related to strings and string substitutions, so it is fairly easy to visualize what is going on in the interpreter. The model is a little different from some other programming languages with which you may already be familiar, so it is worth making sure you understand the basic concepts. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Tcl Commands Tcl stands for Tool Command Language. A command does something for you, like output a string, compute a math expression, or display a widget on the screen. Tcl casts everything into the mold of a command, even programming constructs like variable assignment and procedure definition. Tcl adds a tiny amount of syntax needed to properly invoke commands, and then it leaves all the hard work up to the command implementation. The basic syntax for a Tcl command is: command arg1 arg2 arg3 ... The command is either the name of a built-in command or a Tcl procedure. White space (i.e., spaces or tabs) is used to separate the command name and its arguments, and a newline (i.e., the end of line character) or semicolon is used to terminate a command. Tcl does not interpret the arguments to the commands except to perform grouping, which allows multiple words in one argument, andsubstitution, which is used with programming variables and nested command calls. The behavior of the Tcl command processor can be summarized in three basic steps: Argument grouping. Value substitution of nested commands, variables, and backslash escapes. Command invocation. It is up to the command to interpret its arguments. This model is described in detail in this Chapter. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Hello, World! Example 1-1 The "Hello, World!" example puts stdout {Hello, World!} => Hello, World! In this example, the command is puts, which takes two arguments: an I/O stream identifier and a string . puts writes the string to the I/O stream along with a trailing newline character. There are two points to emphasize: Arguments are interpreted by the command. In the example, stdout is used to identify the standard output stream. The use of stdout as a name is a convention employed byputs and the other I/O commands. Also,stderr is used to identify the standard error output, and stdin is used to identify the standard input.Chapter 9 describes how to open other files for I/O. Curly braces are used to group words together into a single argument. The puts command receives Hello, World! as its second argument. The braces are not part of the value. The braces are syntax for the interpreter, and they get stripped off before the value is passed to the command. Braces group all characters, including newlines and nested braces, until a matching brace is found. Tcl also uses double quotes for grouping. Grouping arguments will be described in more detail later. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Variables The set command is used to assign a value to a variable. It takes two arguments: The first is the name of the variable, and the second is the value. Variable names can be any length, and case is significant. In fact, you can use any character in a variable name. It is not necessary to declare Tcl variables before you use them. The interpreter will create the variable when it is first assigned a value. The value of a variable is obtained later with the dollar-sign syntax, illustrated in Example 1-2: Example 1-2 Tcl variables set var 5 => 5 set b $var => 5 The second set command assigns to variable b the value of variable var. The use of the dollar sign is our first example of substitution. You can imagine that the second set command gets rewritten by substituting the value ofvar for $var to obtain a new command. set b 5 The actual implementation of substitution is more efficient, which is important when the value is large. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Command Substitution The second form of substitution is command substitution. A nested command is delimited by square brackets,[ ]. The Tcl interpreter takes everything between the brackets and evaluates it as a command. It rewrites the outer command by replacing the square brackets and everything between them with the result of the nested command. This is similar to the use of backquotes in other shells, except that it has the additional advantage of supporting arbitrary nesting of commands. Example 1-3 Command substitution set len [string length foobar] => 6 In Example 1-3, the nested command is: string length foobar This command returns the length of the string foobar. The string command is described in detail starting on page 49. The nested command runs first. Then, command substitution causes the outer command to be rewritten as if it were: set len 6 If there are several cases of command substitution within a single command, the interpreter processes them from left to right. As each right bracket is encountered, the command it delimits is evaluated. This results in a sensible ordering in which nested commands are evaluated first so that their result can be used in arguments to the outer command. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Math Expressions The Tcl interpreter itself does not evaluate math expressions. Tcl just does grouping, substitutions and command invocations. The expr command is used to parse and evaluate math expressions. Example 1-4 Simple arithmetic expr 7.2 / 4 => 1.8 The math syntax supported by expr is the same as the C expression syntax. Theexpr command deals with integer, floating point, and boolean values. Logical operations return either 0 (false) or 1 (true). Integer values are promoted to floating point values as needed. Octal values are indicated by a leading zero (e.g., 033 is 27 decimal). Hexadecimal values are indicated by a leading0x. Scientific notation for floating point numbers is supported. A summary of the operator precedence is given on page 20. You can include variable references and nested commands in math expressions. The following example uses expr to add the value of x to the length of the string foobar. As a result of the innermost command substitution, theexpr command sees 6 + 7, and len gets the value 13: Example 1-5 Nested commands set x 7 set len [expr [string length foobar] + $x] => 13 The expression evaluator supports a number of built-in math functions. (For a complete listing, see page 21.) Example 1-6 computes the value of pi: Example 1-6 Built-in math functions set pi [expr 2*asin(1.0)] => 3.1415926535897931 The implementation of expr is careful to preserve accurate numeric values and avoid conversions between numbers and strings. However, you can make expr operate more efficiently by grouping the entire expression in curly braces. The explanation has to do with the byte code compiler that Tcl uses internally, and its effects are explained in more detail on page 15. For now, you should be aware that these expressions are all valid and run faster than the examples shown above: Example 1-7 Grouping expressions with braces expr {7.2 / 4} set len [expr {[string length foobar] + $x}] set pi [expr {2*asin(1.0)}] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks . [ Team LiB ] Backslash Substitution The final type of substitution done by the Tcl interpreter is backslash substitution. This is used to quote characters that have special meaning to the interpreter. For example, you can specify a literal dollar sign, brace, or bracket by quoting it with a backslash. As a rule, however, if you find yourself using lots of backslashes, there is probably a simpler way to achieve the effect you are striving for. In particular, the list command described on page 65 will do quoting for you automatically. InExample 1-8 backslash is used to get a literal$: Example 1-8 Quoting special characters with backslash set dollar \$foo => $foo set x $dollar => $foo Only a single round of interpretation is done. The second set command in the example illustrates an important property of Tcl. The value ofdollar does not affect the substitution performed in the assignment to x. In other words, the Tcl parser does not care about the value of a variable when it does the substitution. In the example, the value of x and dollar is the string $foo. In general, you do not have to worry about the value of variables until you useeval, which is described in Chapter 10. You can also use backslash sequences to specify characters with their Unicode, hexadecimal, or octal value: set escape \u001b set escape \0x1b set escape \033 The value of variable escape is the ASCII ESC character, which has character code27. Table 1-1 on page 20 summarizes backslash substitutions. A common use of backslashes is to continue long commands on multiple lines. This is necessary because a newline terminates a command. The backslash in the next example is required; otherwise the expr command gets terminated by the newline after the plus sign. Example 1-9 Continuing long lines with backslashes set totalLength [expr [string length $one] + \ [string length $two]] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks There are two fine points to escaping newlines. First, if you are grouping an argument as described in the next section, then you do not need to escape newlines; the newlines are automatically part of the group and do not terminate the command. Second, a backslash as the last character in a line is converted into a space, and all the white space at the beginning of the next line is replaced by this substitution. In other words, the backslash-newline sequence also consumes all the leading white space on the next line. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Grouping with Braces and Double Quotes Double quotes and curly braces are used to group words together into one argument. The difference between double quotes and curly braces is that quotes allow substitutions to occur in the group, while curly braces prevent substitutions. This rule applies to command, variable, and backslash substitutions. Example 1-10 Grouping with double quotes vs. braces set s Hello => Hello puts stdout "The length of $s is [string length $s]." => The length of Hello is 5. puts stdout {The length of $s is [string length $s].} => The length of $s is [string length $s]. In the second command of Example 1-10, the Tcl interpreter does variable and command substitution on the second argument toputs. In the third command, substitutions are prevented, so the string is printed as is. In practice, grouping with curly braces is used when substitutions on the argument must be delayed until a later time (or never done at all). Examples include loops, conditional statements, and procedure declarations. Double quotes are useful in simple cases like the puts command previously shown. Another common use of quotes is with the format command. This is similar to the C printf function. The first argument to format is a format specifier that often includes special characters like newlines, tabs, and spaces. The easiest way to specify these characters is with backslash sequences (e.g., \n for newline and \t for tab). The backslashes must be substituted before theformat command is called, so you need to use quotes to group the format specifier. puts [format "Item: %s\t%5.3f" $name $value] Here format is used to align a name and a value with a tab. The%s and %5.3f indicate how the remaining arguments to format are to be formatted. Note that the trailing \n usually found in a C printf call is not needed because puts provides one for us. For more information about the format command, see page 56. Square Brackets Do Not Group The square bracket syntax used for command substitution does not provide grouping. Instead, a nested command is considered part of the current group. In the command below, the double quotes group the last argument, and the nested command is just part of that group. puts stdout "The length of $s is [string length $s]." If an argument is made up of only a nested command, you do not need to group it with double-quotes because the Tcl parser treats the whole nested command as part of the group. puts stdout [string length $s] The following is a redundant use of double quotes: puts stdout "[expr $x + $y]" This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Grouping before Substitution The Tcl parser makes a single pass through a command as it makes grouping decisions and performs string substitutions. Grouping decisions are made before substitutions are performed, which is an important property of Tcl. This means that the values being substituted do not affect grouping because the grouping decisions have already been made. The following example demonstrates how nested command substitution affects grouping. A nested command is treated as an unbroken sequence of characters, regardless of its internal structure. It is included with the surrounding group of characters when collecting arguments for the main command. Example 1-11 Embedded command and variable substitution set x 7; set y 9 puts stdout $x+$y=[expr $x + $y] => 7+9=16 In Example 1-11, the second argument to puts is: $x+$y=[expr $x + $y] The white space inside the nested command is ignored for the purposes of grouping the argument. By the time Tcl encounters the left bracket, it has already done some variable substitutions to obtain: 7+9= When the left bracket is encountered, the interpreter calls itself recursively to evaluate the nested command. Again, the $x and $y are substituted before calling expr. Finally, the result ofexpr is substituted for everything from the left bracket to the right bracket. Theputs command gets the following as its second argument: 7+9=16 Grouping before substitution. The point of this example is that the grouping decision about puts's second argument is made before the command substitution is done. Even if the result of the nested command contained spaces or other special characters, they would be ignored for the purposes of grouping the arguments to the outer command. Grouping and variable substitution interact the same as grouping and command substitution. Spaces or special characters in variable values do not affect grouping decisions because these decisions are made before the variable values are substituted. If you want the output to look nicer in the example, with spaces around the + and =, then you must use double quotes to explicitly group the argument to puts: puts stdout "$x + $y = [expr $x + $y]" This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The double quotes are used for grouping in this case to allow the variable and command substitution on the argument to puts. Grouping Math Expressions with Braces It turns out that expr does its own substitutions inside curly braces. This is explained in more detail on page 15. This means you can write commands like the one below and the substitutions on the variables in the expression still occur: puts stdout "$x + $y = [expr {$x + $y}]" More Substitution Examples If you have several substitutions with no white space between them, you can avoid grouping with quotes. The following command sets concat to the value of variables a, b, and c all concatenated together: set concat $a$b$c Again, if you want to add spaces, you'll need to use quotes: set concat "$a $b $c" In general, you can place a bracketed command or variable reference anywhere. The following computes a command name: [findCommand $x] arg arg When you use Tk, you often use widget names as command names: $text insert end "Hello, World!" [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Procedures Tcl uses the proc command to define procedures. Once defined, a Tcl procedure is used just like any of the other built-in Tcl commands. The basic syntax to define a procedure is: proc name arglist body The first argument is the name of the procedure being defined. The second argument is a list of parameters to the procedure. The third argument is a command body that is one or more Tcl commands. The procedure name is case sensitive, and in fact it can contain any characters. Procedure names and variable names do not conflict with each other. As a convention, this book begins procedure names with uppercase letters and it begins variable names with lowercase letters. Good programming style is important as your Tcl scripts get larger. Tcl coding style is discussed in Chapter 12. Example 1-12 Defining a procedure proc Diag {a b} { set c [expr {sqrt($a * $a + $b * $b)}] return $c } puts "The diagonal of a 3, 4 right triangle is [Diag 3 4]" => The diagonal of a 3, 4 right triangle is 5.0 The Diag procedure defined in the example computes the length of the diagonal side of a right triangle given the lengths of the other two sides. The sqrt function is one of many math functions supported by theexpr command. The variable c is local to the procedure; it is defined only during execution of Diag. Variable scope is discussed further inChapter 7. It is not really necessary to use the variablec in this example. The procedure can also be written as: proc Diag {a b} { return [expr {sqrt($a * $a + $b * $b)}] } The return command is used to return the result of the procedure. Thereturn command is optional in this example because the Tcl interpreter returns the value of the last command in the body as the value of the procedure. So, the procedure could be reduced to: proc Diag {a b} { expr {sqrt($a * $a + $b * $b)} } Note the stylized use of curly braces in the example. The curly brace at the end of the first line starts the third argument to proc, which is the command body. In this case, the Tcl interpreter sees the opening left brace, causing it to ignore newline characters and scan the text until a matching right brace is found. Double quotes have the same property. They group characters, including newlines, until another double quote is found. The result of the grouping is that the third argument to proc is a sequence of commands. When they are evaluated later, the embedded newlines will terminate each command. The other crucial effect of the curly braces around the procedure body is to delay any substitutions in the body until the time the procedure is called. For example, the variables a, b, and c are not defined until the procedure is called, so we do not want to do variable substitution at the time Diag is defined. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The proc command supports additional features such as having variable numbers of arguments and default values for arguments. These are described in detail in Chapter 7. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] A Factorial Example To reinforce what we have learned so far, below is a longer example that uses a while loop to compute the factorial function: Example 1-13 A while loop to compute factorial proc Factorial {x} { set i 1; set product 1 while {$i <= $x} { set product [expr {$product * $i}] incr i } return $product } Factorial 10 => 3628800 The semicolon is used on the first line to remind you that it is a command terminator just like the newline character. The while loop is used to multiply all the numbers from one up to the value of x. The first argument to while is a boolean expression, and its second argument is a command body to execute. The while command and other control structures are described inChapter 6. The same math expression evaluator used by the expr command is used by while to evaluate the boolean expression. There is no need to explicitly use the expr command in the first argument to while, even if you have a much more complex expression. The loop body and the procedure body are grouped with curly braces in the same way. The opening curly brace must be on the same line as proc and while. If you like to put opening curly braces on the line after awhile or if statement, you must escape the newline with a backslash: while {$i < $x} \ { set product ... } Always group expressions and command bodies with curly braces. Curly braces around the boolean expression are crucial because they delay variable substitution until the while command implementation tests the expression. The following example is an infinite loop: set i 1; while $i<=10 {incr i} [*] The loop will run indefinitely. The reason is that the Tcl interpreter will substitute for$i before while is called, so while gets a constant This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks expression 1<=10 that will always be true. You can avoid these kinds of errors by adopting a consistent coding style that groups expressions with curly braces: [*] Ironically, Tcl 8.0 introduced a byte-code compiler, and the first releases of Tcl 8.0 had a bug in the compiler that caused this loop to terminate! This bug is fixed in the 8.0.5 patch release. set i 1; while {$i<=10} {incr i} The incr command is used to increment the value of the loop variablei. This is a handy command that saves us from the longer command: set i [expr {$i + 1}] The incr command can take an additional argument, a positive or negative integer by which to change the value of the variable. Using this form, it is possible to eliminate the loop variable i and just modify the parameterx. The loop body can be written like this: while {$x > 1} { set product [expr {$product * $x}] incr x -1 } Example 1-14 shows factorial again, this time using a recursive definition. A recursive function is one that calls itself to complete its work. Each recursive call decrements x by one, and whenx is one, then the recursion stops. Example 1-14 A recursive definition of factorial proc Factorial {x} { if {$x <= 1} { return 1 } else { return [expr {$x * [Factorial [expr {$x - 1}]]}] } } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] More about Variables The set command will return the value of a variable if it is only passed a single argument. It treats that argument as a variable name and returns the current value of the variable. The dollar-sign syntax used to get the value of a variable is really just an easy way to use the set command. Example 1-15 shows a trick you can play by putting the name of one variable into another variable: Example 1-15 Using set to return a variable value set var {the value of var} => the value of var set name var => var set name => var set $name => the value of var This is a somewhat tricky example. In the last command, $name gets substituted with var. Then, the set command returns the value of var, which is the value of var. Nested set commands provide another way to achieve a level of indirection. The lastset command above can be written as follows: set [set name] => the value of var Using a variable to store the name of another variable may seem overly complex. However, there are some times when it is very useful. There is even a special command, upvar, that makes this sort of trick easier. Theupvar command is described in detail in Chapter 7. Funny Variable Names The Tcl interpreter makes some assumptions about variable names that make it easy to embed variable references into other strings. By default, it assumes that variable names contain only letters, digits, and the underscore. The construct $foo.o represents a concatenation of the value of foo and the literal ".o". If the variable reference is not delimited by punctuation or white space, then you can use curly braces to explicitly delimit the variable name (e.g., ${x}). You can also use this to reference variables with funny characters in their name, although you probably do not want variables named like that. If you find yourself using funny variable names, or computing the names of variables, then you may want to use the upvar command. Example 1-16 Embedded variable references set foo filename . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set object $foo.o => filename.o set a AAA set b abc${a}def => abcAAAdef set .o yuk! set x ${.o}y => yuk!y The unset Command You can delete a variable with theunset command: unset ?-nocomplain? ?--? varName varName2 ... Any number of variable names can be passed to the unset command. However, unset will raise an error if a variable is not already defined, unless the -nocomplain is given. Use -- to unset a variable named -nocomplain. Using info to Find Out about Variables The existence of a variable can be tested with the info exists command. For example, becauseincr requires that a variable exist, you might have to test for the existence of the variable first. Example 1-17 Using info to determine if a variable exists if {![info exists foobar]} { set foobar 0 } else { incr foobar } Example 7-6 on page 92 implements a version of incr which handles this case. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] More about Math Expressions This section describes a few fine points about math in Tcl scripts. In Tcl 7.6 and earlier versions math is not that efficient because of conversions between strings and numbers. The expr command must convert its arguments from strings to numbers. It then does all its computations with double precision floating point values. The result is formatted into a string that has, by default, 12 significant digits. This number can be changed by setting the tcl_precision variable to the number of significant digits desired. Seventeen digits of precision are enough to ensure that no information is lost when converting back and forth between a string and an IEEE double precision number: Example 1-18 Controlling precision with tcl_precision expr 1 / 3 => 0 expr 1 / 3.0 => 0.333333333333 set tcl_precision 17 => 17 expr 1 / 3.0 # The trailing 1 is the IEEE rounding digit => 0.33333333333333331 In Tcl 8.0 and later versions, the overhead of conversions is eliminated in most cases by the built-in compiler. Even so, Tcl was not designed to support math-intensive applications. You may want to implement math-intensive code in a compiled language and register the function as a Tcl command as described in Chapter 47. There is support for string comparisons by expr, so you can test string values inif statements. You must use quotes so thatexpr knows to do string comparisons: if {$answer == "yes"} { ... } However, the string compare and string equal commands described in Chapter 4 are more reliable because expr may do conversions on strings that look like numbers. The issues with string operations and expr are discussed on page 52. Tcl 8.4 introducedeq and ne expr operators to allow strict string based comparison. Expressions can include variable and command substitutions and still be grouped with curly braces. This is because an argument to expr is subject to two rounds of substitution: one by the Tcl interpreter, and a second by expr itself. Ordinarily this is not a problem because math values do not contain the characters that are special to the Tcl interpreter. The second round of substitutions is needed to support commands like while and if that use the expression evaluator internally. Grouping expressions can make them run more efficiently. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks You should always group expressions in curly braces and let expr do command and variable substitutions. Otherwise, your values may suffer extra conversions from numbers to strings and back to numbers. Not only is this process slow, but the conversions can lose precision in certain circumstances. For example, suppose x is computed from a math function: set x [expr {sqrt(2.0)}] At this point the value ofx is a double-precision floating point value, just as you would expect. If you do this: set two [expr $x * $x] then you may or may not get 2.0 as the result! This is because Tcl will substitute $x and expr will concatenate all its arguments into one string, and then parse the expression again. In contrast, if you do this: set two [expr {$x * $x}] then expr will do the substitutions, and it will be careful to preserve the floating point value ofx. The expression will be more accurate and run more efficiently because no string conversions will be done. The story behind Tcl values is described in more detail in Chapter 47 on C programming and Tcl. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Comments Tcl uses the pound character, #, for comments. Unlike in many other languages, the# must occur at the beginning of a command. A# that occurs elsewhere is not treated specially. An easy trick to append a comment to the end of a command is to precede the # with a semicolon to terminate the previous command: # Here are some parameters set rate 7.0 ;# The interest rate set months 60 ;# The loan term One subtle effect to watch for is that a backslash effectively continues a comment line onto the next line of the script. In addition, a semicolon inside a comment is not significant. Only a newline terminates comments: # Here is the start of a Tcl comment \ and some more of it; still in the comment The behavior of a backslash in comments is pretty obscure, but it can be exploited as shown inExample 2-3 on page 27. A surprising property of Tcl comments is that curly braces inside comments are still counted for the purposes of finding matching brackets. The motivation for this odd feature was to keep the original Tcl parser simpler. However, it means that the following will not work as expected to comment out an alternate version of an if expression: # if {boolean expression1} { if {boolean expression2} { some commands } The previous sequence results in an extra left curly brace, and probably a complaint about a missing close brace at the end of your script! A technique I use to comment out large chunks of code is to put the code inside an if block that will never execute: if {0} { unused code here } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Substitution and Grouping Summary The following rules summarize the fundamental mechanisms of grouping and substitution that are performed by the Tcl interpreter before it invokes a command: Command arguments are separated by white space, unless arguments are grouped with curly braces or double quotes as described below. Grouping with curly braces, { }, prevents substitutions. Braces nest. The interpreter includes all characters between the matching left and right brace in the group, including newlines, semicolons, and nested braces. The enclosing (i.e., outermost) braces are not included in the group's value. Grouping with double quotes, " ", allows substitutions. The interpreter groups everything until another double quote is found, including newlines and semicolons. The enclosing quotes are not included in the group of characters. A double-quote character can be included in the group by quoting it with a backslash, (e.g., \"). Grouping decisions are made before substitutions are performed, which means that the values of variables or command results do not affect grouping. A dollar sign, $, causes variable substitution. Variable names can be any length, and case is significant. If variable references are embedded into other strings, or if they include characters other than letters, digits, and the underscore, they can be distinguished with the ${varname} syntax. Square brackets, [ ], cause command substitution. Everything between the brackets is treated as a command, and everything including the brackets is replaced with the result of the command. Nesting is allowed. The backslash character, \, is used to quote special characters. You can think of this as another form of substitution in which the backslash and the next character or group of characters are replaced with a new character. Substitutions can occur anywhere unless prevented by curly brace grouping. Part of a group can be a constant string, and other parts of it can be the result of substitutions. Even the command name can be affected by substitutions. A single round of substitutions is performed before command invocation. The result of a substitution is not interpreted a second time. This rule is important if you have a variable value or a command result that contains special characters such as spaces, dollar signs, square brackets, or braces. Because only a single round of substitution is done, you do not have to worry about special characters in values causing extra substitutions. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Fine Points A common error is to forget a space between arguments when grouping with braces or quotes. This is because white space is used as the separator, while the braces or quotes only provide grouping. If you forget the space, you will get syntax errors about unexpected characters after the closing brace or quote. The following is an error because of the missing space between } and {: if {$x > 1}{puts "x = $x"} A double quote is only used for grouping when it comes after white space. This means you can include a double quote in the middle of a group without quoting it with a backslash. This requires that curly braces or white space delimit the group. I do not recommend using this obscure feature, but this is what it looks like: set silly a"b When double quotes are used for grouping, the special effect of curly braces is turned off. Substitutions occur everywhere inside a group formed with double quotes. In the next command, the variables are still substituted: set x xvalue set y "foo {$x} bar" => foo {xvalue} bar When double quotes are used for grouping and a nested command is encountered, the nested command can use double quotes for grouping, too. puts "results [format "%f %f" $x $y]" Spaces are not required around the square brackets used for command substitution. For the purposes of grouping, the interpreter considers everything between the square brackets as part of the current group. The following sets x to the concatenation of two command results because there is no space between ] and [. set x [cmd1][cmd2] Newlines and semicolons are ignored when grouping with braces or double quotes. They get included in the group of characters just like all the others. The following sets x to a string that contains newlines: set x "This is line one. This is line two. This is line three." During command substitution, newlines and semicolons are significant as command terminators. If you have a long command that is nested in square brackets, put a backslash before the newline if you want to continue the command on another line. This was illustrated in Example 1-9 on page 8. A dollar sign followed by something other than a letter, digit, underscore, or left parenthesis is treated as a literal dollar sign. The following sets x to the single character $. set x $ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Reference Backslash Sequences Table 1-1. Backslash sequences \a Bell. (0x7) \b Backspace. (0x8) \f Form feed. (0xc) \n Newline. (0xa) \r Carriage return. (0xd) \t Tab. (0x9) \v Vertical tab. (0xb) \<newline> Replace the newline and the leading white space on the next line with a space. \\ Backslash. ('\') \ooo Octal specification of character code. 1, 2, or 3 octal digits (0-7). \xhh Hexadecimal specification of character code. 1 or 2 hex digits. Be careful when using this in a string of characters, because all hexadecimal characters following the \x will be consumed, but only the last 2 will specify the value. \uhhhh Hexadecimal specification of a 16-bit Unicode character value. 4 hex digits. \c Replaced with literal c if c is not one of the cases listed above. In particular, \$, \", \{, \}, \], and \[ are used to obtain these characters. Arithmetic Operators Table 1-2. Arithmetic operators from highest to lowest precedence -~! Unary minus, bitwise NOT, logical NOT. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks */% Multiply, divide, remainder. +- Add, subtract. << >> Left shift, right shift. < > <= >= Comparison: less, greater, less or equal, greater or equal. == != eq ne Equal, not equal, string equal (Tcl 8.4), string not equal (Tcl 8.4). & Bitwise AND. ^ Bitwise XOR. | Bitwise OR. && Logical AND. || Logical OR. x?y:z If x then y else z. Built-in Math Functions Table 1-3. Built-in math functions acos(x) Arccosine of x. asin(x) Arcsine of x. atan(x) Arctangent of x. atan2(y,x) Rectangular (x,y) to polar (r,th). atan2 gives th. ceil(x) Least integral value greater than or equal tox. cos(x) Cosine of x. cosh(x) Hyperbolic cosine of x. exp(x) x Exponential, e . floor(x) Greatest integral value less than or equal tox. fmod(x,y) Floating point remainder of x/y. hypot(x,y) Returns sqrt(x*x + y*y). r part of polar coordinates. log(x) Natural log of x. log10(x) Log base 10 of x. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks pow(x,y) x to the y power, xy. sin(x) Sine of x. sinh(x) Hyperbolic sine of x. sqrt(x) Square root of x. tan(x) Tangent of x. tanh(x) Hyperbolic tangent of x. abs(x) Absolute value of x. double(x) Promote x to floating point. int(x) Truncate x to an integer. round(x) Round x to an integer. rand() Return a random floating point value between 0.0 and 1.0. srand(x) Set the seed for the random number generator to the integerx. wide(x) Promote x to a wide (64-bit) integer. (Tcl 8.4) Core Tcl Commands The pages listed in Table 1-4 give the primary references for the command. Table 1-4. Built-in Tcl commands Command after Pg. 228 Description Schedule a Tcl command for later execution. append 56 Append arguments to a variable's value. No spaces added. array 97 Query array state and search through elements. binary 59 Convert between strings and binary data. break 83 Exit loop prematurely. catch 83 Trap errors. cd 122 Change working directory. clock 183 Get the time and format date strings. close 121 Close an open I/O stream. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Command Pg. Description concat 65 Concatenate arguments with spaces between. Splices lists. console 29 Control the console used to enter commands interactively. continue 83 Continue with next loop iteration. error 85 Raise an error. eof 116 Check for end of file. eval 130 Concatenate arguments and evaluate them as a command. exec 105 Fork and execute a UNIX program. exit 124 Terminate the process. expr 6 Evaluate a math expression. fblocked 233 Poll an I/O channel to see if data is ready. fconfigure 231 Set and query I/O channel properties. fcopy 250 Copy from one I/O channel to another. file 108 Query the file system. fileevent 229 Register callback for event-driven I/O. flush 116 Flush output from an I/O stream's internal buffers. for 82 Loop construct similar to C for statement. foreach 79 Loop construct over a list, or lists, of values. format 56 Format a string similar to Csprintf. gets 119 Read a line of input from an I/O stream. glob 122 Expand a pattern to matching file names. global 90 history 196 Declare global variables. Use command-line history. if 76 Test a condition. Allowselse and elseif clauses. incr 12 Increment a variable by an integer amount. info 186 Query the state of the Tcl interpreter. interp 292 Create additional Tcl interpreters. join 72 Concatenate list elements with a given separator string. lappend 66 Add elements to the end of a list. lindex 68 Fetch an element of a list. linsert 68 Insert elements into a list. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Command Pg. Description list 65 Create a list out of the arguments. llength 68 Return the number of elements in a list. load 697 Load shared libraries that define Tcl commands. lrange 68 Return a range of list elements. lreplace 68 Replace elements of a list. lsearch 69 Search for an element of a list that matches a pattern. lset 62 Set an element in a list. (Tcl 8.4) lsort 70 Sort a list. namespace 213 Create and manipulate namespaces. open 116 Open a file or process pipeline for I/O. package 175 Provide or require code packages. pid 124 Return the process ID. proc 87 Define a Tcl procedure. puts 119 Output a string to an I/O stream. pwd 122 Return the current working directory. read 120 Read blocks of characters from an I/O stream. regexp 158 Match regular expressions. regsub 162 Substitute based on regular expressions. rename 88 Change the name of a Tcl command. return 86 Return a value from a procedure. scan 58 Parse a string according to a format specification. seek 121 set 5 Set the seek offset of an I/O stream. Assign a value to a variable. socket 239 Open a TCP/IP network connection. source 26 Evaluate the Tcl commands in a file. split 71 Chop a string up into list elements. string 49 Operate on strings. subst 140 switch 77 tell 121 Substitute embedded commands and variable references. Test several conditions. Return the current seek offset of an I/O stream. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Command Pg. Description time 202 Measure the execution time of a command. trace 193 Monitor variable assignments. unknown 178 Handle unknown commands. unset uplevel upvar 13 Delete variables. 138 Execute a command in a different scope. 91 Reference a variable in a different scope. variable 207 Declare namespace variables. vwait 230 Wait for a variable to be modified. while 79 [ Team LiB ] Loop until a boolean expression is false. . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 2. Getting Started This chapter explains how to run Tcl and Tk on different operating system platforms: UNIX, Windows, and Macintosh. Tcl commands discussed are: source, console and info. This chapter explains how to run Tcl scripts on different computer systems. While you can write Tcl scripts that are portable among UNIX, Windows, and Macintosh, the details about getting started are different for each system. If you are looking for a current version of Tcl/Tk, use the CD-ROM or check the URLs listed in the Preface on page liv. The main Tcl/Tk program is wish. Wish stands for windowing shell, and with it you can create graphical applications that run on all these platforms. The name of the program is a little different on each of the UNIX, Windows, and Macintosh systems. On UNIX it is just wish. On Windows you will find wish.exe, and on the Macintosh the application name isWish. A version number may also be part of the name, such as wish8.0, wish83.exe, or Wish 8.4. The differences among versions are introduced on pagelii, and described in more detail inPart VII of the book. This book will use wish to refer to all of these possibilities. Tk adds Tcl commands that are used to create graphical user interfaces, and Tk is described in Part III. You can run Tcl without Tk if you do not need a graphical interface, such as with the CGI script discussed in Chapter 3. In this case the program is tclsh, tclsh.exe or Tclsh. When you run wish, it displays an empty window and prompts for a Tcl command with a % prompt. You can enter Tcl commands interactively and experiment with the examples in this book. On Windows and Macintosh, a console window is used to prompt for Tcl commands. On UNIX, your terminal window is used. As described later, you can also set up standalone Tcl/Tk scripts that are self-contained applications. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The source Command It is a good idea to try out the examples in this book as you read along. The highlighted examples from the book are on the CD-ROM in the exsource folder. You can edit these scripts in your favorite editor. Save your examples to a file and then execute them with the Tcl source command: source filename The source command reads Tcl commands from a file and evaluates them just as if you had typed them interactively. Chapter 3 develops a sample application. To get started, just open an editor on a file namedcgi1.tcl. Each time you update this file you can save it, reload it into Tcl with the source command, and test it again. Development goes quickly because you do not wait for things to compile! [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] UNIX Tcl Scripts On UNIX you can create a standalone Tcl or Tcl/Tk script much like an sh or csh script. The trick is in the first line of the file that contains your script. If the first line of a file begins with #!pathname, then UNIX uses pathname as the interpreter for the rest of the script. The "Hello, World!" program from Chapter 1 is repeated in Example 2-1 with the special starting line: Example 2-1 A standalone Tcl script on UNIX #!/usr/local/bin/tclsh puts stdout {Hello, World!} The Tk hello world program from Chapter 23 is shown in Example 2-2: Example 2-2 A standalone Tk script on UNIX #!/usr/local/bin/wish button .hello -text Hello -command {puts "Hello, World!"} pack .hello -padx 10 -pady 10 The actual pathnames for tclsh and wish may be different on your system. If you type the pathname for the interpreter wrong, you receive a confusing "command not found" error. You can find out the complete pathname of the Tcl interpreter with the info nameofexecutable command. This is what appears on my system: info nameofexecutable => /home/welch/install/linux-ix86/bin/tclsh8.4 Watch out for long pathnames. On most UNIX systems, this special first line is limited to 32 characters, including the #!. If the pathname is too long, you may end up with /bin/sh trying to interpret your script, giving you syntax errors. You might try using a symbolic link from a short name to the true, long name of the interpreter. However, watch out for systems like older versions of Solaris in which the script interpreter cannot be a symbolic link. Fortunately, Solaris doesn't impose a 32-character limit on the pathname, so you can just use a long pathname. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The next example shows a trick that works around the pathname length limitation in all cases. The trick comes from a posting to comp.lang.tcl by Kevin Kenny. It takes advantage of a difference between comments in Tcl and the Bourne shell. Tcl comments are described on page 16. In the example, the exec Bourne shell command that runs the Tcl interpreter is hidden in a comment as far as Tcl is concerned, but it is visible to /bin/sh. The exec command (in /bin/sh) replaces the current program, so that is all that the Bourne shell processes; Tcl interprets the rest of the script. Example 2-3 Using /bin/sh to run a Tcl script #!/bin/sh # The backslash makes the next line a comment in Tcl \ exec /some/very/long/path/to/wish "$0" ${1+"$@"} # ... Tcl script goes here ... You do not even have to know the complete pathname oftclsh or wish to use this trick. You can just do the following: #!/bin/sh # Run wish from the users PATH \ exec wish -f "$0" ${1+"$@"} The drawback of an incomplete pathname is that many sites have different versions of wish and tclsh that correspond to different versions of Tcl and Tk. In addition, some users may not have these programs in their PATH. You can hide more than one Bourne shell command in a script with this trick. For example, you might need to set environment variables: #!/bin/sh #\ export LD_LIBRARY_PATH=/usr/local/lib #\ exec /usr/local/bin/tclsh "$0" ${1+"$@"} [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Windows Start Menu You can add your Tcl/Tk programs to the Windows start menu. The command is the complete name of the wish.exe program and the name of the script. The trick is that the name of wish.exe has a space in it in the default configuration, so you must use quotes. Your start command will look something like this: "c:\Program Files\Tcl84\wish84.exe" "c:\My Files\script.tcl" This starts c:\My Files\script.tcl as a standalone Tcl/Tk program. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] Macintosh OS 8/9 and ResEdit If you want to create a self-contained Tcl/Tk application on Macintosh OS 8 or 9, you must copy the Wish program and add a Macintosh resource named tclshrc that has the start-up Tcl code. The Tcl code can be a singlesource command that reads your script file. Here are step-by-step instructions to create the resource using ResEdit: First, make a copy of Wish and open the copy in ResEdit. Pull down the Resource menu and select Create New Resource operation to make a newTEXT resource. ResEdit opens a window and you can type in text. Type in asource command that names your script: source "Hard Disk:Tcl/Tk 8.3:Applications:MyScript.tcl" Set the name of the resource to be tclshrc. You do this through theGet Resource Info dialog under the Resources menu in ResEdit. This sequence of commands is captured in an application called Drag n Drop Tclets, which comes with the Macintosh Tcl distribution. If you drag a Tcl script onto this icon, it will create a copy of Wish and create the tclshrc text resource that has asource command that will load that script. If you have a Macintosh development environment, you can build a version of Wish that has additional resources built right in. You add the resources to the applicationInit.r file. If a resource contains Tcl code, you use it like this: source -rcrc resource If you don't want to edit resources, you can just use theWish Source menu to select a script to run. Macintosh OS X Mac OS X can run the same Tcl/Tk as Macintosh system 8 or 9. However, the preferred version for Mac OS X is Tcl/Tk Aqua, which uses the native windowing system known as Aqua. There are some differences in the application structure due to the new application framework used when building this variant. Wish checks the Resources/Scripts directory in its application bundle for a file calledAppMain.tcl, if found it is used as the startup script and the Scripts folder is added to the auto_path. This is similar in spirit to the tclshrc resource described above. Daniel Steffen deserves a great deal of credit for the Tcl/Tk Aqua port and his continued support of the Macintosh platform. He has put together a great distribution that includes many popular extensions, which you can find on the CD-ROM. You can find out more about Tcl/Tk on Macintosh through these URLs: http://wiki.tcl.tk/macos/ http://www.maths.mq.edu.au/~steffen/tcltk/ This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The console Command The Windows and Macintosh platforms have a built-in console that is used to enter Tcl commands interactively. You can control this console with the console command. The console is visible by default. Hide the console like this: console hide Display the console like this: console show The console is implemented by a second Tcl interpreter. You can evaluate Tcl commands in that interpreter with: console eval command There is an alternate version of this console called TkCon. It is included on the CD-ROM, and you can find current versions on the Internet. TkCon was created by Jeff Hobbs and has lots of nice features. You can useTkCon on Unix systems, too. Some of its features were added to console in 8.4. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Command-Line Arguments If you run a script from the command line, for example from a UNIX shell, you can pass the script command-line arguments. You can also specify these arguments in the shortcut command in Windows. For example, under UNIX you can type this at a shell: % myscript.tcl arg1 arg2 arg3 In Windows, you can have a shortcut that runs wish on your script and also passes additional arguments: "c:\Program Files\Tcl84\wish.exe" c:\your\script.tcl arg1 The Tcl shells pass the command-line arguments to the script as the value of the argv variable. The number of command-line arguments is given by the argc variable. The name of the program, or script, is not part ofargv nor is it counted byargc. Instead, it is put into theargv0 variable. Table 2-2 lists all the predefined variables in the Tcl shells. argv is a list, so you can use thelindex command, which is described on page 63, to extract items from it: set arg1 [lindex $argv 0] The following script prints its arguments (foreach is described on page 79): Example 2-4 The EchoArgs script # Tcl script to echo command line arguments puts "Program: $argv0" puts "Number of arguments: $argc" set i 0 foreach arg $argv { puts "Arg $i: $arg" incr i } Command-Line Options to Wish Some command-line options are interpreted by wish, and they do not appear in theargv variable. The general form of the wish command line is: wish ?options? ?script? ?arg1 arg2? If no script is specified, then wish just enters an interactive command loop.Table 2-1 lists the options that wish supports: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 2-1. Wish command line options -colormap new Use a new private colormap. See page 624. -display display Use the specified Xdisplay. UNIX only. -geometry geometry The size and position of the window. See page 658. -name name Specify the Tk application name. See page 648. -sync Run X synchronously. UNIX only. -use id Use the window specified byid for the main window. See page 667. -visual visual Specify the visual for the main window. See page 624. -- Terminate options to wish. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than [ Team LiB ] Predefined Variables Table 2-2. Variables defined by tclsh and wish argc The number of command-line arguments. argv A list of the command-line arguments. argv0 The name of the script being executed. If being used interactively,argv0 is the name of the shell program. embed_args The list of arguments in the <EMBED> tag. Tcl applets only. See page 314. env An array of the environment variables. See page 124. tcl_interactive True (one) if the tclsh is prompting for commands. tcl_library The script library directory. tcl_patchLevel Modified version number, e.g., 8.0b1. tcl_platform Array containing operating system information. See page 192. tcl_prompt1 If defined, this is a command that outputs the prompt. tcl_prompt2 If defined, this is a command that outputs the prompt if the current command is not yet complete. tcl_version Version number. auto_path The search path for script library directories. See page 172. auto_index A map from command name to a Tcl command that defines it. auto_noload If set, the library facility is disabled. auto_noexec If set, the auto execute facility is disabled. geometry (wish only). The value of the -geometry argument. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 3. The Guestbook CGI Application This chapter presents a simple Tcl program that computes a Web page. The chapter provides a brief background to HTML and the CGI interface to Web servers. The chapter uses the ncgi package from the standard Tcl library. This chapter presents a complete, but simple, guestbook program that computes an HTML document, or Web page, based on the contents of a simple database. The basic idea is that a user with a Web browser visits a page that is computed by the program. The details of how the page gets from your program to the user with the Web browser vary from system to system. The Tcl Web Server described in Chapter 18 comes with this guestbook example already set up. You can also use these scripts on your own Web server, but you will need help from your Webmaster to set things up. The chapter provides a very brief introduction to HTML and CGI programming. HTML is a way to specify text formatting, including hypertext links to other pages on the World Wide Web. CGI is a standard for communication between a Web server that delivers documents and a program that computes documents for the server. There are many books on these subjects alone. A guestbook is a place for visitors to sign their name and perhaps provide other information. We will build a guestbook that takes advantage of the World Wide Web. Our guests can leave their address as a Universal Resource Location (URL). The guestbook will be presented as a page that has hypertext links to all these URLs so that other guests can visit them. The program works by keeping a simple database of the guests, and it generates the guestbook page from the database. The Tcl scripts described in this chapter use commands and techniques that are described in more detail in later chapters. The goal of the examples is to demonstrate the power of Tcl without explaining every detail. If the examples in this chapter raise questions, you can follow the references to examples in other chapters that do go into more depth. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] A Quick Introduction to HTML Web pages are written in a text markup language called HTML (HyperText Markup Language). The idea of HTML is that you annotate, or mark up, regular text with special tags that indicate structure and formatting. For example, the title of a Web page is defined like this: <TITLE>My Home Page</TITLE> The tags provide general formatting guidelines, but the browsers that display HTML pages have freedom in how they display things. This keeps the markup simple. The general syntax for HTML tags is: <tag parameters>normal text</tag> As shown here, the tags usually come in pairs. The open tag may have some parameters, and the close tag name begins with a slash. The case of a tag is not considered, so <title>, <Title>, and <TITLE> are all valid and mean the same thing. The corresponding close tag could be </title>, </Title>, </TITLE>, or even </TiTlE>. The <A> tag defines hypertext links that reference other pages on the Web. The hypertext links connect pages into a Web so that you can move from page to page to page and find related information. It is the flexibility of the links that makes the Web so interesting. The <A> tag takes an HREF parameter that defines the destination of the link. If you wanted to link to my home page, you would put this in your page: <A HREF="http://www.beedub.com/">Brent Welch</A> When this construct appears in a Web page, your browser typically displays "Brent Welch" in blue underlined text. When you click on that text, your browser switches to the page at the address "http://www.beedub.com/". There is a lot more to HTML, of course, but this should give you a basic idea of what is going on in the examples. Table 3-1 summarizes the HTML tags that will be used in the examples: Table 3-1. HTML tags used in the examples HTML Main tag that surrounds the whole document. HEAD Delimits head section of the HTML document. TITLE Defines the title of the page. BODY Delimits the body section. Lets you specify page colors. H1 - H6 HTML defines 6 heading levels: H1, H2, H3, H4, H5, H6. P Start a new paragraph. BR One blank line. B Bold text. I Italic text. A Used for hypertext links. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks IMG Specify an image. DL Definition list. DT Term clause in a definition list. DD Definition clause in a definition list. UL An unordered list. LI A bulleted item within a list. TABLE Create a table. TR A table row. TD A cell within a table row. FORM Defines a data entry form. INPUT A one-line entry field, checkbox, radio button, or submit button. TEXTAREA A multiline text field. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] CGI for Dynamic Pages There are two classes of pages on the Web: static and dynamic. A static page is written and stored on a Web server, and the same thing is returned each time a user views the page. This is the easy way to think about Web pages. You have some information to share, so you compose a page and tinker with the HTML tags to get the information to look good. If you have a home page, it is probably in this class. In contrast, a dynamic page is computed each time it is viewed. This is how pages that give up-to-the-minute stock prices work, for example. A dynamic page does not mean it includes animations; it just means that a program computes the page contents when a user visits the page. The advantage of this approach is that a user might see something different each time he or she visits the page. As we shall see, it is also easier to maintain information in a database of some sort and generate the HTML formatting for the data with a program. A CGI (Common Gateway Interface) program is used to compute Web pages. The CGI standard defines how inputs are passed to the program as well as a way to identify different types of results, such as images, plain text, or HTML markup. A CGI program simply writes the contents of the document to its standard output, and the Web server takes care of delivering the document to the user's Web browser. Example 3-1 is a very simple CGI script: Example 3-1 A simple CGI script puts "Content-Type: text/html" puts "" puts "<TITLE>The Current Time</TITLE>" puts "The time is <B>[clock format [clock seconds]]</B>" The program computes a simple HTML page that has the current time. Each time a user visits the page, she will see the current time on the server. The server that has the CGI program and the user viewing the page might be on different sides of the planet. The output of the program is divided into two sections: the protocol header and the page contents. In this simple example, the protocol header just has a Content-Type line that tells your Web browser what kind of data comes next. A blank line separates the protocol header from the page, which starts with a <TITLE> tag, in this case. The clock command is used twice: once to get the current time in seconds, and a second time to format the time into a nice-looking string. The clock command is described in detail on page 183. Fortunately, there is no conflict between the markup syntax used by HTML and the Tcl syntax for embedded commands, so we can mix the two in the argument to the puts command. Double quotes are used to group the argument to puts so that the clock command will be executed. Example 3-2 shows what the output of the program will look like: Example 3-2 Output of Example 3-1 Content-Type: text/html <TITLE>The Current Time</TITLE> The time is <B>Wed Jul 10 14:29:36 2002</B> This example is a bit sloppy in its use of HTML, but it should display properly in most Web browsers. Example 3-3 includes all the required This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks tags for a proper HTML document. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The guestbook.cgi Script The guestbook.cgi script computes a page that lists all the registered guests.Example 3-3 is shown first, and then each part of it is discussed in more detail later. One thing to note right away is that the HTML tags are generated by procedures that hide the details of the HTML syntax. The first lines of the script use the UNIX trick to have tclsh interpret the script. This is described on page 26: Example 3-3 The guestbook.cgi script, version 1 #!/bin/sh # guestbook.cgi # Implement a simple guestbook page. # The set of visitors is kept in a simple database. # The newguest.cgi script will update the database. #\ exec tclsh "$0" ${1+"$@"} # The guestbook.data file has the database # The datafile is in the same directory as the script set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] puts "text/html" puts "" set title "Brent's Guestbook" puts "<HTML><HEAD><TITLE>$title</TITLE></HEAD>" puts "<BODY BGCOLOR=white TEXT=black>" puts "<H1>$title</H1>" if {![file exists $datafile]} { puts "No registered guests, yet. <P> Be the first <A href='newguest.html'>registered guest!</A>" } else { puts "The following folks have registered in my GuestBook. <P> <A href='newguest.html'>Register</A> <H2>Guests</H2>" catch {source $datafile} foreach name [lsort [array names Guestbook]] { set item $Guestbook($name) set homepage [lindex $item 0] set markup [lindex $item 1] puts "<H3><A href=$homepage>$name</A></H3>" . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks puts $markup } } puts "</BODY></HTML>" Using a Script Library File If you write one CGI script, you are likely to write several. You could start making copies and modifying your first script, but that quickly becomes hard to maintain. If you learn something new after writing your third script, will you remember to update the first two scripts you wrote? Probably not. The best way to approach this problem is to create a collection of Tcl procedures in a file that you share among all your CGI scripts. The Standard Tcl Library, tcllib, provides several packages of procedures that you can use. Later in this chapter, we will look at thencgi package that helps handle form data. Before we do that, let's start a simple collection of our own procedures and learn how to share them among several different CGI scripts. Suppose you have a file cgihacks.tcl that contains your Tcl procedures. The source command loads that file into your script. The naive approach shown here probably won't work: source cgihacks.tcl Loading a file from the same directory as your script The problem is that the current directory of the CGI process may not be the same as the directory that contains the CGI script or the cgihacks.tcl file. You can use the info script command to find out where the CGI script is, and from that load the supporting file. Thefile dirname and file join commands manipulate file names in a platform-independent way. They are described on page 108. I use the following trick to avoid putting absolute file names into my scripts, which would have to be changed if the program moves later: set dir [file dirname [info script]] source [file join $dir cgihacks.tcl] You can also create script libraries as described in Chapter 12. That chapter describes tools to create an index of procedures so an application can quickly load the procedures it needs, and how to create packages of procedures so you can keep your code organized. However you set them up, it is always a good idea to have a library of procedures you share with other applications. Beginning the HTML Page The way you start your HTML page is a great candidate for capturing in a Tcl procedure. For example, I like to have the page title appear in This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks the TITLE tag in the head, and repeated in an H1 tag at the beginning of the body. You may also have a favorite set of colors or fonts that you want to specify in the BODY tag. By putting all this into a Tcl procedure, you can make it easy to share this among all your scripts. If your tastes change tomorrow, then you can change the Tcl procedure in one spot and affect all CGI scripts that share the procedure. Example 3-4 shows Cgi_Header that generates a simple standard page header: Example 3-4 The Cgi_Header procedure proc Cgi_Header {title {body {bgcolor=white text=black}}} { puts stdout "Content-Type: text/html <HTML> <HEAD> <TITLE>$title</TITLE> </HEAD> <BODY $body> <H1>$title</H1>" } The Cgi_Header procedure takes as arguments the title for the page and some optional parameters for the HTML BODY tag. The procedure definition uses the syntax for an optional parameter, so you do not have to pass bodyparams to Cgi_Header. The default specifies black text on a white background to avoid the standard gray background of most browsers. Default values for procedure parameters are described on page 87. Example 3-5 The guestbook.cgi script, version 2 #!/bin/sh # guestbook.cgi # Implement a simple guestbook page. # The set of visitors is kept in a simple database. # The newguest.cgi script will update the database. #\ exec tclsh "$0" ${1+"$@"} # The guestbook.data file has the database # The datafile is in the same directory as the script set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] # Load our supporting Tcl procedures to define Cgi_Header source [file join $dir cgihacks.tcl] Cgi_Header "Brent's Guestbook" if {![file exists $datafile]} { puts "No registered guests, yet. <P> Be the first <A href='newguest.html'>registered guest!</A>" } else { puts "The following folks have registered in my GuestBook. . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks <P> <A href='newguest.html'>Register</A> <h2>Guests</h2>" catch {source $datafile} foreach name [lsort [array names Guestbook]] { set item $Guestbook($name) set homepage [lindex $item 0] set markup [lindex $item 1] puts "<H3><A href=$homepage>$name</A></H3>" puts $markup } } puts "</BODY></HTML>" Example 3-5 is a new version of the original CGI script that loads thecgihacks.tcl file and uses Cgi_Header. The Cgi_Header procedure just contains a single puts command that generates the standard boilerplate that appears at the beginning of the output. Note that several lines are grouped together with double quotes. Double quotes are used so that the variable references mixed into the HTML are substituted properly. The output of the Cgi_Header procedure matches what we wrote by hand inExample 3-3. Sample Output of the CGI Script The program tests to see whether there are any registered guests or not. The file command, which is described in detail on page 108, is used to see whether there is any data. The exclamation point means "not" in a boolean expression: if {![file exists $datafile]} { If the database file does not exist, a different page is displayed to encourage a registration. The page includes a hypertext link to a registration page, newguest.html, which is described on page 43. The output of the program would be as below inExample 3-6 if there were no data file: Example 3-6 Initial output of guestbook.cgi with no data Content-Type: text/html <HTML> <HEAD> <TITLE>Brent's Guestbook</TITLE> </HEAD> <BODY BGCOLOR=white TEXT=black> <H1>Brent's Guestbook</H1> <P> No registered guests. <P> Be the first <A HREF="newguest.html">registered guest!</A> </BODY></HTML> Note the inconsistent indentation of the HTML that comes from the indentation in the puts command used for that part of the page. The browser doesn't care about white space in the HTML. You have a choice between lining up the Tcl commands in your CGI script, or lining up the HTML output. Here we have two different examples. The Cgi_Header procedure produces output that is lined up, but the procedure This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks definition looks a bit odd. The main script, in contrast, keeps its Tcl commands neatly indented, but that shows up in the output. If you generate most of your HTML from code, you may choose to keep your code tidy. Example 3-7 shows the output of the guestbook.cgi script when there is some data in the data file: Example 3-7 Output of guestbook.cgi with guestbook data Content-Type: text/html <HTML> <HEAD> <TITLE>Brent's Guestbook</TITLE> </HEAD> <BODY BGCOLOR=white TEXT=black> <H1>Brent's Guestbook</H1> <P> The following folks have registered in my guestbook. <P> <A HREF='newguest.html'>Register</A> <H2>Guests</H2> <H3><A HREF="http://www.beedub.com/">Brent Welch</A></H3> <IMG SRC="http://www.beedub.com/welch.gif"> </BODY></HTML> Using a Tcl Array for the Database The data file contains Tcl commands that define an array that holds the guestbook data. If this file is kept in the same directory as the guestbook.cgi script, then you can compute its name: set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] By using Tcl commands to represent the data, we can load the data with the source command. The catch command is used to protect the script from a bad data file, which will show up as an error from the source command. Catching errors is described in detail on page 85: catch {source $datafile} The Guestbook variable is the array defined inguestbook.data. Array variables are the topic ofChapter 8. Each element of the array is defined with a Tcl command that looks like this: set Guestbook(key) {url markup} The person's name is the array index, or key. The value of the array element is a Tcl list with two elements: their URL and some additional HTML markup that they can include in the guestbook. Tcl lists are the topic of Chapter 5. The following example shows what the command looks like with real data: set {Guestbook(Brent Welch)} { This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks http://www.beedub.com/ {<img src=http://www.beedub.com/welch.gif>} } The spaces in the name result in additional braces to group the whole variable name and each list element. This syntax is explained on page 96. Do not worry about it now. We will see on page 46 that all the braces in the previous statement are generated automatically. The main point is that the person's name is the key, and the value is a list with two elements. The array names command returns all the indices, or keys, in the array, and thelsort command sorts these alphabetically. The foreach command loops over the sorted list, setting the loop variable x to each key in turn: foreach name [lsort [array names Guestbook]] { The lsort command will sort the names based on the person's first name. You can havelsort sort things in a variety of ways. One trick we can use here is to have lsort treat each key as a list and sort on the last item in the list (i.e., the last name): foreach name [lsort -index end [array names Guestbook]] { The lsort command is described in more detail on page 70. Theforeach command assigns name to each key of the Guestbook array. We get the value like this: set item $Guestbook($name) The two list elements are extracted withlindex, which is described on page 68. set homepage [lindex $item 0] set markup [lindex $item 1] We generate the HTML for the guestbook entry as a level-three header that contains a hypertext link to the guest's home page. We follow the link with any HTML markup text that the guest has supplied to embellish his or her entry: puts "<H3><a href=$homepage>$name</a></H3>" puts $markup The homepage and markup variables are not strictly necessary, and the code could be written more compactly without them. However, the variables make the code more understandable. Here is what it looks like without the temporary variables: puts "<H3><a href=[lindex $item 0]>$name</a></H3>" puts [lindex $item 1] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Defining Forms and Processing Form Data The guestbook.cgi script only generates output. The other half of CGI deals with input from the user. Input is more complex for two reasons. First, we have to define another HTML page that has a form for the user to fill out. Second, the data from the form is organized and encoded in a standard form that must be decoded by the script. Example 3-8 on page 43 defines a very simple form, and the procedure that decodes the form data is shown in Example 11-6 on page 165. The guestbook page contains a link to newguest.html. This page contains a form that lets a user register his or her name, home page URL, and some additional HTML markup. The form has a submit button. When a user clicks that button in her browser, the information from the form is passed to the newguest.cgi script. This script updates the database and computes another page for the user that acknowledges the user's contribution. The newguest.html Form An HTML form contains tags that define data entry fields, buttons, checkboxes, and other elements that let the user specify values. For example, a one-line entry field that is used to enter the home page URL is defined like this: <INPUT TYPE=text NAME=url> The INPUT tag is used to define several kinds of input elements, and itstype parameter indicates what kind. In this case,TYPE=text creates a one-line text entry field. The submit button is defined with an INPUT tag that has TYPE=submit, and the VALUE parameter becomes the text that appears on the submit button: <INPUT TYPE=submit NAME=submit VALUE=Register> A general type-in window is defined with the TEXTAREA tag. This creates a multiline, scrolling text field that is useful for specifying lots of information, such as a free-form comment. In our case, we will let guests type in HTML that will appear with their guestbook entry. The text between the open and close TEXT-AREA tags is inserted into the type-in window when the page is first displayed. <TEXTAREA NAME=markup ROWS=10 COLS=50>Hello.</TEXTAREA> A common parameter to the form tags is NAME=something. This name identifies the data that will come back from the form. The tags also have parameters that affect their display, such as the label on the submit button and the size of the text area. Those details are not important for our example. The complete form is shown in Example 3-8: Example 3-8 The newguest.html form <HTML> <HEAD> . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks <TITLE>Register in my Guestbook</TITLE> </HEAD> <BODY BGCOLOR=white TEXT=black> <FORM ACTION="newguest.cgi" METHOD="POST"> <H1>Register in my Guestbook</H1> <UL> <LI>Name <INPUT TYPE="text" NAME="name" SIZE="40"> <LI>URL <INPUT TYPE="text" NAME="url" SIZE="40"> <P> If you don't have a home page, you can use an email URL like "mailto:welch@acm.org" <LI>Additional HTML to include after your link: <BR> <TEXTAREA NAME="html" COLS="60" ROWS="15"> </TEXTAREA> <LI><INPUT TYPE="submit" NAME="new" VALUE="Add me to your guestbook"> <LI><INPUT TYPE="submit" NAME="update" VALUE="Update my guestbook entry"> </UL> </FORM> </BODY> </HTML> The ncgi and cgi.tcl Packages The newguest.cgi script uses the ncgi package to process form data. This is one of many packages available in the Standard Tcl Library, commonly known as "tcllib". If you don't have tcllib installed, you can find it on the CD-ROM, on SourceForge at www.sf.net/projects/tcllib, or via the main www.tcl.tk Web site. If your Tcl installation includes tcllib, then you use thepackage command to load the package. package require ncgi The procedures in the ncgi package are in thencgi namespace. Tcl namespaces are described in detail inChapter 14. Procedures in a namespace are qualified with the name of the namespace and :: syntax. For example, the standard setup procedure for a CGI script is ncgi::parse. The "n" in ncgi is for "new". Don Libes wrote the original package for CGI scripts known as cgi.tcl. There is also the cgilib.tcl package that contains Cgi_Header and some other procedures described in earlier editions of this book. The ncgi and html packages of tcllib provide most of the features in both cgi.tcl and cgilib.tcl, but follow the standard namespace conventions use by the packages in tcllib. You can still find cgi.tcl on the Web at http://expect.nist.gov/cgi.tcl/ The newguest.cgi Script This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks When the user clicks the Submit button in her browser, the data from the form is passed to the program identified by the ACTION parameter of the form tag. That program takes the data, does something useful with it, and then returns a new page for the browser to display. In our case, the FORM tag names newguest.cgi as the program to handle the data: <FORM ACTION=newguest.cgi METHOD=POST> The CGI specification defines how the data from the form is passed to the program. The data is encoded and organized so that the program can figure out the values the user specified for each form element. The encoding is handled rather nicely with some regular expression tricks that are done in ncgi::parse. ncgi::parse saves the form data, and ncgi::value gets a form value in the script. These procedures are described in Example 11-6 on page 165. Example 3-9 starts out by calling ncgi::parse: Example 3-9 The newguest.cgi script #!/bin/sh #\ exec tclsh "$0" ${1+"$@"} # Use the ncgi package from tcllib to process form data package require ncgi ncgi::parse # Load our data file and supporting procedures set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] source [file join $dir cgihacks.tcl] # Open the datafile in append mode if {[catch {open $datafile a} out]} { Cgi_Header "Guestbook Registration Error" \ {BGCOLOR=black TEXT=red} puts "<P>Cannot open the data file<P>" puts $out;# the error message exit 0 } # Append a Tcl set command that defines the guest's entry puts $out "" puts $out [list set Guestbook([ncgi::value name]) \ [list [ncgi::value url] [ncgi::value html]]] close $out # Return a page to the browser Cgi_Header "Guestbook Registration Confirmed" \ {BGCOLOR=white TEXT=black} puts " <TABLE BORDER=1> This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks <TR><TD>Name</TD> <TD>[ncgi::value name]</TD></TR> <TR><TD>URL</TD> <TD><A HREF='[ncgi::value url]'>[ncgi::value url]</A></TD></TR> <TR><TD>Extra HTML</TD> <TD>[ncgi::value html]</TD></TR> </TABLE> " puts </BODY></HTML> Using Tcl Scripts to Store Data The main idea of the newguest.cgi script is that it saves the data to a file as a Tcl command that defines an element of the Guestbook array. This lets the guestbook.cgi script simply load the data by using the Tclsource command. This trick of storing data as a Tcl script saves us from the chore of defining a new file format and writing code to parse it. Instead, we can rely on the well-tuned Tcl implementation to do the hard work for us efficiently. The script opens the datafile in append mode so that it can add a new record to the end. Opening files is described in detail on page 116. The script uses a catch command to guard against errors. If an error occurs, a page explaining the error is returned to the user. Working with files is one of the most common sources of errors (permission denied, disk full, file-not-found, and so on), so I always open the file inside a catch statement: if {[catch {open $datafile a} out]} { # an error occurred } else { # open was ok } In this command, the variable out gets the result of the open command, which is either a file descriptor or an error message. This style of using catch is described in detail in Example 6-14 on page 83. Use list to generate Tcl commands. The script writes the data as a Tcl set command. The list command is used to format the data properly: puts $out [list set Guestbook([ncgi::value name]) \ [list [ncgi::value url] [ncgi::value html]]] There are two lists. First, the url and html values are formatted into one list. This list will be the value of the array element. Then the whole Tcl command is formed as a list. In simplified form, the command is generated from this: list set variable value Using the list command ensures that the result will always be a valid Tcl command that sets the variable to the given value. This is a very important technique. If you want to generate Tcl commands, the best way to do it is to generate lists using list manipulation commands. The This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks list command is described in more detail on page 65. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Handling Errors in CGI Scripts One of the more frustrating aspects of CGI programming is that errors in your script result in blank browser pages, and it may be difficult or impossible to find any trace of the error message. The other main problem is that your Web server may not be configured properly to find your CGI script. I use two simple tricks to track down the source of these errors. The first trick simply verifies that my script has run at all by creating an empty file somewhere on the Web server. On a UNIX system, you can put this line at the beginning of your script: close [open /tmp/my_cgi_script_ran w] When you aim the browser at your CGI script, it should at least create the file. If not, then the Web server cannot find your script, or it cannot find the Tclsh required by your script. Double-check your setup and the #! line in your script. On Windows, your best bet may be to use the TclHttpd Web server, which has a built-in ability to run Tcl CGI scripts. TclHttpd has other even cooler ways to generate pages, too. If your script suddenly stops working after you've modified it, then you have introduced a programming bug. I generally put all of the script into a catch statement and print out any errors that occur. That way the errors will be displayed by the browser instead of filed into the void by your Web server. Example 3-10 shows the newguest.cgi script rewritten so the catch statement surrounds all the statements. At the end, the value of the errorInfo variable is printed out if an error has occurred: Example 3-10 The newguest.cgi script with error handling #!/bin/sh #\ exec tclsh "$0" ${1+"$@"} # Trap all errors if {[catch { # Use the ncgi package from tcllib to process form data package require ncgi ncgi::parse # Load our data file and supporting procedures set dir [file dirname [info script]] set datafile [file join $dir guestbook.data] source [file join $dir cgihacks.tcl] # Open the datafile in append mode set out [open $datafile a] # Append a Tcl set command that defines the guest's entry puts $out "" This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks puts $out [list set Guestbook([ncgi::value name]) \ [list [ncgi::value url] [ncgi::value html]]] close $out # Return a page to the browser Cgi_Header "Guestbook Registration Confirmed" \ {BGCOLOR=white TEXT=black} puts " <TABLE BORDER=1> <TR><TD>Name</TD> <TD>[ncgi::value name]</TD></TR> <TR><TD>URL</TD> <TD><A HREF='[ncgi::value url]'>[ncgi::value url]</A></TD></TR> <TR><TD>Extra HTML</TD> <TD>[ncgi::value html]</TD></TR> </TABLE> </BODY></HTML> " # End of main script } err]} { # Error occurred - display in the Web page puts "Content-Type: text/plain" puts "" puts "CGI error occurred in [info script]" puts $errorInfo } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Next Steps There are a number of details that can be added to this example. Users may want to update their entry, for example. They could do that now, but they would have to retype everything. They might also like a chance to check the results of their registration and make changes before committing them. This requires another page that displays their guest entry as it would appear on a page, and also has the fields that let them update the data. The details of how a CGI script is hooked up with a Web server vary from server to server. You should ask your local Webmaster for help if you want to try this out on your local Web site. The Tcl Web Server comes with this guestbook example already set up, plus it has a number of other very interesting ways to generate pages. My own taste in Web page generation has shifted from CGI to a template-based approach supported by the Tcl Web Server. This is the topic of Chapter 18. The next few chapters describe basic Tcl commands and data structures. We return to the CGI example inChapter 11 on regular expressions. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 4. String Processing in Tcl This chapter describes string manipulation and simple pattern matching. Tcl commands described are: string, append, format, scan, and binary. The string command is a collection of several useful string manipulation operations. Strings are the basic data item in Tcl, so it should not be surprising that there are a large number of commands to manipulate strings. A closely related topic is pattern matching, in which string comparisons are made more powerful by matching a string against a pattern. This chapter describes a simple pattern matching mechanism that is similar to that used in many other shell languages. Chapter 11 describes a more complex and powerful regular expression pattern matching mechanism. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than [ Team LiB ] The string Command The string command is really a collection of operations you can perform on strings. The following example calculates the length of the value of a variable. set name "Brent Welch" string length $name => 11 The first argument to string determines the operation. You can askstring for valid operations by giving it a bad one: [View full width] string junk => bad option "junk": should be bytelength, compare, equal, first, index, is, last, length , map, match, range, repeat, replace, tolower, totitle, toupper, trim, trimleft, trimright, wordend, or wordstart This trick of feeding a Tcl command bad arguments to find out its usage is common across many commands. Table 4-1 summarizes the string command. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 4-1. The string command string bytelength str Returns the number of bytes used to store a string, which may be different from the character length returned by string length because of UTF-8 encoding. See page 220 of Chapter 15 about Unicode and UTF-8. string compare ?-nocase? Compares strings lexicographically. Use -nocase for case insensitive comparison. Use-length to limit the ?-length len? str1 str2 comparison to the first len characters. Returns 0 if equal, -1 ifstr1 sorts before str2, else 1. string equal ?-nocase? Compares strings and returns 1 if they are the same. Use-nocase for case insensitive comparison. str1 str2 string first subString string Returns the index in string of the first occurrence of subString, or -1 if string is not found. startIndex may be ?startIndex? specified to start in the middle of string. string index string index Returns the character at the specifiedindex. An index counts from zero. Useend for the last character. string is class ?-strict? ?-failindex varname? Returns 1 if string belongs to class. If -strict, then empty strings never match, otherwise they always match. If string a member of class. See Table 4-3 on page 54 for character class names. string last subString string Returns the index in string of the last occurrence of subString, or -1 if subString is not found. startIndex may be ?startIndex? specified to start in the middle of string. string length string Returns the number of characters in string. string map ?-nocase? Returns a new string created by mapping characters in string according to the input, output list incharMap. charMap string See page 55. string match ?-nocase? Returns 1 if str matches the pattern, else 0. Glob-style matching is used. See page 53. -failindex is specified, then varname is assigned the index of the character instring that prevented it from being pattern str string range str i j Returns the range of characters instr from i to j. string repeat str count Returns str repeated count times. string replace str first last Returns a new string created by replacing charactersfirst through last with newstr, or nothing. ?newstr? string tolower string ?first? Returns string in lower case. first and last determine the range of string on which to operate. ?last? string totitle string ?first? Capitalizes string by replacing its first character with the Unicode title case, or upper case, and the rest with ?last? lower case. first and last determine the range of string on which to operate. string toupper string ?first? Returns string in upper case. first and last determine the range of string on which to operate. ?last? string trim string ?chars? Trims the characters in chars from both ends of string. chars defaults to whitespace. string trimleft string Trims the characters in chars from the beginning of string. chars defaults to whitespace. ?chars? string trimright string Trims the characters in chars from the end of string. chars defaults to whitespace. ?chars? string wordend str ix Returns the index in str of the character after the word containing the character at indexix. string wordstart str ix Returns the index in str of the first character in the word containing the character at indexix. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks These are the string operations I use most: The equal operation, which is shown inExample 4-2 on page 53. String match. This pattern matching operation is described on page 53. The tolower, totitle, and toupper operations convert case. The trim, trimright, and trimleft operations are handy for cleaning up strings. These new operations were added in Tcl 8.1 (actually, they first appeared in the 8.1.1 patch release): The equal operation, which is simpler than usingstring compare. The is operation that test for kinds of strings. String classes are listed inTable 4-3 on page 54. The map operation that translates characters (e.g., like the Unixtr command.) The repeat and replace operations. The totitle operation, which is handy for capitalizing words. String Indices Several of the string operations involve string indices that are positions within a string. Tcl counts characters in strings starting with zero. The special index end is used to specify the last character in a string: string range abcd 2 end => cd Tcl 8.1 added syntax for specifying an index relative to the end. Specify end-N to get the Nth character before the end. For example, the following command returns a new string that drops the first and last characters from the original: string range $string 1 end-1 There are several operations that pick apart strings: first, last, wordstart, wordend, index, and range. If you find yourself using combinations of these operations to pick apart data, it may be faster if you can do it with the regular expression pattern matcher described in Chapter 11. Strings and Expressions Strings can be compared with expr, if, and while using the comparison operators eq, ne, ==, !=, < and >. However, there are a number of subtle issues that can cause problems. First, you must quote the string value so that the expression parser can identify it as a string type. Then, you This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than must group the expression with curly braces to prevent the double quotes from being stripped off by the main interpreter: if {$x == "foo"} command expr is only reliable for string comparison when usingeq or ne. Despite the quotes, the expression operators that work on numbers and strings first convert try converting items to numbers if possible, and then converts them back if it detects a case of string comparison. The conversion back is always done as a decimal number. This can lead to unexpected conversions between strings that look like hexadecimal or octal numbers. The following boolean expression is true! if {"0xa" == "10"} { puts stdout ack! } => ack! A safe way to compare strings is to use the string compare and string equal operations. The eq and ne expr operators were introduced in 8.4 to allow more compact strict string comparison. These operations also work faster because the unnecessary conversions are eliminated. Like the C library strcmp function, string compare returns 0 if the strings are equal, minus 1 if the first string is lexicographically less than the second, or 1 if the first string is greater than the second: Example 4-1 Comparing strings with string compare if {[string compare $s1 $s2] == 0} { # strings are equal } The string equal command added in Tcl 8.1 makes this simpler: Example 4-2 Comparing strings with string equal if {[string equal $s1 $s2]} { # strings are equal } The eq operator added in Tcl 8.4 is semantically equal, but more compact. It also avoids any internal format conversions. There is also ane operator to efficiently test for inequality. Example 4-3 Comparing strings with eq This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks if {$s1 eq $s2} { # strings are equal } String Matching The string match command implements glob-style pattern matching that is modeled after the file name pattern matching done by various UNIX shells. The heritage of the word "glob" is rooted in UNIX, and Tcl preserves this historical oddity in the glob command that does pattern matching on file names. The glob command is described on page 122. Table 4-2 shows the three constructs used instring match patterns: Table 4-2. Matching characters used with string match * Match any number of any characters. ? Match exactly one character. [chars] Match any character in chars. Any other characters in a pattern are taken as literals that must match the input exactly. The following example matches all strings that begin with a: string match a* alpha => 1 To match all two-letter strings: string match ?? XY => 1 To match all strings that begin with either a or b: string match {[ab]*} cello => 0 Be careful! Square brackets are also special to the Tcl interpreter, so you will need to wrap the pattern up in curly braces to prevent it from being interpreted as a nested command. Another approach is to put the pattern into a variable: set pat {[ab]*x} string match $pat box => 1 You can specify a range of characters with the syntax [x-y]. For example, [a-z] represents the set of all lower-case letters, and [0-9] represents all the digits. You can include more than one range in a set. Any letter, digit, or the underscore is matched with: string match {[a-zA-Z0-9_]} $char The set matches only a single character. To match more complicated patterns, like one or more characters from a set, then you need to use regular expression matching, which is described on page 158. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than If you need to include a literal *, ?, or bracket in your pattern, preface it with a backslash: string match {*\?} what? => 1 In this case the pattern is quoted with curly braces because the Tcl interpreter is also doing backslash substitutions. Without the braces, you would have to use two backslashes. They are replaced with a single backslash by Tcl before string match is called. string match *\\? what? Character Classes The string is command tests a string to see whether it belongs to a particularclass. This is useful for input validation. For example, to make sure something is a number, you do: if {![string is integer -strict $input]} { error "Invalid input. Please enter a number." } Classes are defined in terms of the Unicode character set, which means they are more general than specifying character sets with ranges over the ASCII encoding. For example, alpha includes many characters outside the range of[A-Za-z] because of different characters in other alphabets. The classes are listed in Table 4-3. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 4-3. Character class names alnum Any alphabet or digit character. alpha Any alphabet character. ascii Any character with a 7-bit character code (i.e., less than 128.) boolean A valid Tcl boolean value, such as0, 1, true, false (in any case). control Character code less than 32, and not NULL. digit Any digit character. double A valid floating point number. false A valid Tcl boolean false value, such as0 or false (in any case). graph Any printing characters, not including space characters. integer A valid integer. lower A string in all lower case. print A synonym for alnum. punct Any punctuation character. space Space, tab, newline, carriage return, vertical tab, backspace. true A valid Tcl boolean true value, such as1 or true (in any case). upper A string all in upper case. wordchar Alphabet, digit, and the underscore. xdigit Valid hexadecimal digits. Mapping Strings The string map command translates a string based on a character map. The map is in the form of a input, output list. Wherever a string contains an input sequence, that is replaced with the corresponding output. For example: string map {f p d l} food => pool The inputs and outputs can be more than one character and they do not have to be the same length: string map {f p d ll oo u} food => pull This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than Example 4-4 is more practical. It uses string map to replace fancy quotes and hyphens produced by Microsoft Word into ASCII equivalents. It uses the open, read, and close file operations that are described inChapter 9, and the fconfigure command described on page 234 to ensure that the file format is UNIX friendly. Example 4-4 Mapping Microsoft World special characters to ASCII proc Dos2Unix {filename} { set input [open $filename] set output [open $filename.new] fconfigure $output -translation lf puts $output [string map { \223 " \224 " \222 ' \226 } [read $input]] close $input close $output } [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The append Command The append command takes a variable name as its first argument and concatenates its remaining arguments onto the current value of the named variable. The variable is created if it does not already exist: set foo z append foo a b c set foo => zabc The append command is efficient with large strings. The append command provides an efficient way to add items to the end of a string. It modifies a variable directly, so it can exploit the memory allocation scheme used internally by Tcl. Using the append command like this: append x " some new stuff" is always faster than this: set x "$x some new stuff" The lappend command described on page 65 has similar performance benefits when working with Tcl lists. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The format Command The format command is similar to the C printf function. It formats a string according to a format specification: format spec value1 value2 ... The spec argument includes literals and keywords. The literals are placed in the result as is, while each keyword indicates how to format the corresponding argument. The keywords are introduced with a percent sign, %, followed by zero or more modifiers, and terminate with a conversion specifier. The most general keyword specification for each argument contains up to six parts: position specifier flags field width precision word length conversion character Example keywords include %f for floating point, %d for integer, and %s for string format. Use%% to obtain a single percent character. The following examples use double quotes around the format specification. This is because often the format contains white space, so grouping is required, as well as backslash substitutions like \t or \n, and the quotes allow substitution of these special characters.Table 4-4 lists the conversion characters: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 4-4. Format conversions d Signed integer. u Unsigned integer. i Signed integer. The argument may be in hex (0x) or octal (0) format. o Unsigned octal. x or X Unsigned hexadecimal. 'x' gives lowercase results. c Map from an integer to the ASCII character it represents. s A string. f Floating point number in the formata.b. e or E Floating point number in scientific notation, a.bE+-c. g or G Floating point number in either%f or %e format, whichever is shorter. A position specifier is i$, which means take the value from argumenti as opposed to the normally corresponding argument. The position counts from 1. If a position is specified for one format keyword, the position must be used for all of them. If you group the format specification with double quotes, you need to quote the $ with a backslash: set lang 2 format "%${lang}\$s" one un uno => un The position specifier is useful for picking a string from a set, such as this simple language-specific example. The message catalog facility described in Chapter 15 is a much more sophisticated way to solve this problem. The position is also useful if the same value is repeated in the formatted string. The flags in a format are used to specify padding and justification. In the following examples, the # causes a leading 0x to be printed in the hexadecimal value. The zero in 08 causes the field to be padded with zeros. Table 4-5 summarizes the format flag characters. format "%#x" 20 => 0x14 format "%#08x" 10 => 0x0000000a After the flags you can specify a minimum field width value. The value is padded to this width with spaces, or with zeros if the 0 flag is used: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 4-5. Format flags - Left justify the field. + Always include a sign, either + or -. space Precede a number with a space, unless the number has a leading sign. Useful for packing numbers close together. 0 Pad with zeros. # Leading 0 for octal. Leading 0x for hex. Always include a decimal point in floating point. Do not remove trailing zeros (%g). format "%-20s %3d" Label 2 => Label 2 You can compute a field width and pass it to format as one of the arguments by using* as the field width specifier. In this case the next argument is used as the field width instead of the value, and the argument after that is the value that gets formatted. set maxl 8 format "%-*s = %s" $maxl Key Value => Key = Value The precision comes next, and it is specified with a period and a number. For %f and %e it indicates how many digits come after the decimal point. For %g it indicates the total number of significant digits used. For%d and %x it indicates how many digits will be printed, padding with zeros if necessary. format "%6.2f %6.2d" 1 1 => 1.00 01 The storage length part comes last but it only became useful in Tcl 8.4 where wide integer support was added. Otherwise Tcl maintains all floating point values in double-precision, and all integers as long words. Wide integers are a minimum of 64-bits wide. By adding the l (long) word length specifier, we can see the difference between regular and wide integers. format %u -1 => 4294967295 format %lu -1 => 18446744073709551615 [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The scan Command The scan command parses a string according to a format specification and assigns values to variables. It returns the number of successful conversions it made, unless no capture variables are given, in which case it returns the scan matches in a list. The general form of the command is: scan string format ?var? ?var? ?var? ... The format for scan is nearly the same as in theformat command. The %c scan format converts one character to its decimal value. The scan format includes a set notation. Use square brackets to delimit a set of characters. The set matches one or more characters that are copied into the variable. A dash is used to specify a range. The following scans a field of all lowercase letters. scan abcABC {%[a-z]} result => 1 set result => abc If the first character in the set is a right square bracket, then it is considered part of the set. If the first character in the set is ^, then characters not in the set match. Again, put a right square bracket immediately after the^ to include it in the set. Nothing special is required to include a left square bracket in the set. As in the previous example, you will want to protect the format with braces, or use backslashes, because square brackets are special to the Tcl parser. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than [ Team LiB ] The binary Command Tcl 8.0 added support for binary strings. Previous versions of Tcl used null-terminated strings internally, which foils the manipulation of some types of data. Tcl now uses counted strings, so it can tolerate a null byte in a string value without truncating it. This section describes the binary command that provides conversions between strings and packed binary data representations. Thebinary format command takes values and packs them according to a template. For example, this can be used to format a floating point vector in memory suitable for passing to Fortran. The resulting binary value is returned: binary format template value ?value ...? The binary scan command extracts values from a binary string according to a similar template. For example, this is useful for extracting data stored in binary data file. It assigns values to a set of Tcl variables: binary scan value template variable ?variable ...? Format Templates The format template consists of type keys and counts. The count is interpreted differently depending on the type. For types like integer (i) and double (d), the count is a repetition count (e.g.,i3 means three integers). For strings, the count is a length (e.g.,a3 means a three-character string). If no count is specified, it defaults to 1. If count is *, then binary scan uses all the remaining bytes in the value. Several type keys can be specified in a template. Each key-count combination moves an imaginary cursor through the binary data. There are special type keys to move the cursor. The x key generates null bytes inbinary format, and it skips over bytes inbinary scan. The @ key uses its count as an absolute byte offset to which to set the cursor. As a special case,@* skips to the end of the data. The X key backs up count bytes. The types are summarized in Table 4-6. In the table, count is the optional count following the type letter. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than Table 4-6. Binary conversion types a A character string of length count. Padded with nulls inbinary format. A A character string of length count. Padded with spaces inbinary format. Trailing nulls and blanks are discarded inbinary scan. b A binary string of length count. Low-to-high order. B A binary string of length count. High-to-low order. h A hexadecimal string of length count. Low-to-high order. H A hexadecimal string of length count. High-to-low order. (More commonly used thanh.) c An 8-bit character code. Thecount is for repetition. s A 16-bit integer in little-endian byte order. Thecount is for repetition. S A 16-bit integer in big-endian byte order. Thecount is for repetition. i A 32-bit integer in little-endian byte order. Thecount is for repetition. I A 32-bit integer in big-endian byte order. Thecount is for repetition. f Single-precision floating point value in native format.Thecount is for repetition. d Double-precision floating point value in native format. Thecount is for repetition. w A 64-bit integer in little-endian byte order. Thecount is for repetition. (Tcl 8.4) W A 64-bit integer in big-endian byte order. Thecount is for repetition. (Tcl 8.4) x Pack count null bytes with binary format. Skip count bytes with binary scan. X Backup count bytes. @ Skip to absolute position specified bycount. If count is *, skip to the end. Numeric types have a particular byte order that determines how their value is laid out in memory. The type keys are lowercase for little-endian byte order (e.g., Intel) and uppercase for big-endian byte order (e.g., SPARC and Motorola). Different integer sizes are 16-bit (s or S), 32-bit (i or I), and, with Tcl 8.4 or greater, 64-bit (w or W). Note that the official byte order for data transmitted over a network is big-endian. Floating point values are always machine-specific, so it only makes sense to format and scan these values on the same machine. There are three string types: character (a or A), binary (b or B), and hexadecimal (h or H). With these types the count is the length of the string. The a type pads its value to the specified length with null bytes inbinary format and the A type pads its value with spaces. If the value is too long, it is truncated. In binary scan, the A type strips trailing blanks and nulls. A binary string consists of zeros and ones. The b type specifies bits from low-to-high order, and theB type specifies bits from high-to-low order. A hexadecimal string specifies 4 bits (i.e., nybbles) with each character. The h type specifies nybbles from low-to-high order, and theH type specifies nybbles from high-to-low order. The B and H formats match the way you normally write out numbers. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Examples When you experiment with binary format and binary scan, remember that Tcl treats things as strings by default. A "6", for example, is the character 6 with character code 54 or 0x36. The c type returns these character codes: set input 6 binary scan $input "c" 6val set 6val => 54 You can scan several character codes at a time: binary scan abc "c3" list => 1 set list => 97 98 99 The previous example uses a single type key, so binary scan sets one corresponding Tcl variable. If you want each character code in a separate variable, use separate type keys: binary scan abc "ccc" x y z => 3 set z => 99 Use the H format to get hexadecimal values: binary scan 6 "H2" 6val set 6val => 36 Use the a and A formats to extract fixed width fields. Here the * count is used to get all the rest of the string. Note thatA trims trailing spaces: binary scan "hello world " a3x2A* first second puts "\"$first\" \"$second\"" => "hel" " world" Use the @ key to seek to a particular offset in a value. The following command gets the second double-precision number from a vector. Assume the vector is read from a binary data file: binary scan $vector "@8d" double With binary format, the a and A types create fixed width fields. A pads its field with spaces, if necessary. The value is truncated if the string is too long: binary format "A9A3" hello world => hello wor An array of floating point values can be created with this command: binary format "f*" 1.2 3.45 7.43 -45.67 1.03e4 Remember that floating point values are always in native format, so you have to read them on the same type of machine that they were created. With integer data you specify either big-endian or little-endian formats. The tcl_platform variable described on page 193 can tell you the byte order of the current platform. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than Binary Data and File I/O When working with binary data in files, you need to turn off the newline translations and character set encoding that Tcl performs automatically. These are described in more detail on pages 120 and 219. For example, if you are generating binary data, the following command puts your standard output in binary mode: fconfigure stdout -translation binary -encoding binary puts [binary format "B8" 11001010] [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Related Chapters To learn more about manipulating data in Tcl, read about lists inChapter 5 and arrays in Chapter 8. For more about pattern matching, read about regular expressions in Chapter 11. For more about file I/O, see Chapter 9. For information on Unicode and other Internationalization issues, seeChapter 15. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 5. Tcl Lists This chapter describes Tcl lists. Tcl commands described are: list, lindex, llength, lrange, lappend, linsert, lreplace, lsearch, lset, lsort, concat, join, and split. Lists in Tcl have the same structure as Tcl commands. All the rules you learned about grouping arguments in Chapter 1 apply to creating valid Tcl lists. However, when you work with Tcl lists, it is best to think of lists in terms of operations instead of syntax. Tcl commands provide operations to put values into a list, get elements from lists, count the elements of lists, replace elements of lists, and so on. It is a good habit to use commands like list and lappend to construct lists, instead of creating them by hand. Lists are used with commands such asforeach that take lists as arguments. In addition, lists are important when you are building up a command to be evaluated later. Delayed command evaluation with eval is described in Chapter 10, and similar issues with Tk callback commands are described inChapter 30. However, Tcl lists are not often the right way to build complicated data structures in scripts. You may find Tcl arrays more useful, and they are the topic of Chapter 8. List operations are also not right for handling unstructured data such as user input. Use regular expressions instead, which are described in Chapter 11. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Tcl Lists A Tcl list is a sequence of values. When you write out a list, it has the same syntax as a Tcl command. A list has its elements separated by white space. Braces or quotes can be used to group words with white space into a single list element. Because of the relationship between lists and commands, the list-related commands described in this chapter are used often when constructing Tcl commands. Since Tcl 8.0, lists are really 1-dimensional object arrays. Early versions of Tcl represented all values as strings. Lists were just strings with special syntax to group their elements. The string representation was parsed on each list access, so you could have performance problems with large lists. The performance of lists was improved by the Tcl compiler added in Tcl 8.0. The Tcl runtime now stores lists using an C array of pointers to each element. (The Tcl_Obj type is described on page 694.) Tcl can access any element in the list with the same cost. Appending new elements to a list is made efficient by over allocating the array so there is room to grow. The internal format also records the number of list elements, so getting the length of a list is cheap. However, you can still get into performance trouble if you use a big Tcl list like a string, e.g., for output. Tcl will convert the list into a string representation if you print it to a file, or manipulate it with string commands. Table 5-1 describes Tcl commands for lists. Table 5-1. List-related commands list arg1 arg2 ... Creates a list out of all its arguments. lindex list ?i ...? Returns the ith element from list. Specifying multiple index elements allows you to descend into nested lists easily. llength list Returns the number of elements in list. lrange list i j Returns the ith through jth elements from list. lappend listVar arg ... Appends elements to the value of listVar. linsert list index arg Inserts elements into list before the element at position index. Returns a new list. arg ... lreplace list i j arg arg ... Replaces elements i through j of list with the args. Returns a new list. lsearch ?options? list Returns the index of the element in list that matches the value according to the options. Glob matching is the value default. Returns -1 if not found. lset listVar ?i ...? newValue Set the ith element in variable listVar to newValue. (Tcl 8.4) This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks lsort ?switches? list Sorts elements of the list according to the switches: -ascii, -dictionary, -integer, -real, -increasing, -decreasing, -index ix, -unique, -command command. Returns a new list. concat list list ... Joins multiple lists together into one list. join list joinString Merges the elements of a list together by separating them withjoinString. split string splitChars Splits a string up into list elements, using the characters insplitChars as boundaries between list elements. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Constructing Lists Constructing a list can be tricky if you try to write the proper list syntax by hand. The manual approach works for simple cases. In more complex cases, however, you should use Tcl commands that build lists. Using list commands eliminates the struggle to get the grouping and quoting right, and the list is maintained in an efficient internal format. If you create lists by hand with quoting, there is additional overhead to parse the string representation the first time you use the list. The list command The list command constructs a list out of its arguments so that there is one list element for each argument. The simple beauty of list is that any special characters in the list elements do not matter. Spaces inside an element do not cause it to become more than one list element. The list command is efficient, too. It doesn't matter if list is making a list of three single-character values, or three 10 kilobyte values. The cost to make that three element list is the same in either case. The most compelling uses of list involve making lists out of variables that could have arbitrary values, as shown in Example 5-1. Example 5-1 Constructing a list with the list command set x {1 2} => 1 2 set y \$foo => $foo set l1 [list $x "a b" $y] => {1 2} {a b} {$foo} set l2 [list $l1 $x] => {{1 2} {a b} {$foo}}} {1 2} The list command does automatic quoting. The first list, l1, has three elements. The values of the elements do not affect the list structure. The second list,l2, has two elements, the value of l1 and the value of x. Internally Tcl shares values instead of making copies, so constructing lists out of other values is quite efficient. When you first experiment with Tcl lists, the treatment of curly braces can be confusing. In the assignment to x, for example, the curly braces This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks disappear. However, they seem to come back again when $x is put into a bigger list. Also, the double quotes arounda b get changed into curly braces. What's going on? There are three steps in the process. In the first step, the Tcl parser groups arguments to the list command. In the grouping process, the braces and quotes are syntax that define groups. These syntax characters get stripped off. The braces and quotes are not part of the values being grouped. In the second step, the list command creates an internal list structure. This is an array of references to each value. In the third step the value is printed out. This step requires conversion of the list into a string representation. The string representation of the list uses curly braces to group values back into list elements. The lappend Command The lappend command is used to append elements to the end of a list. The first argument tolappend is the name of a Tcl variable, and the rest of the arguments are added to the variable's value as new list elements. Like list, lappend operates efficiently on the internal representation of the list value. It is always more efficient to use lappend than to try and append elements by hand. Example 5-2 Using lappend to add elements to a list lappend new 1 2 => 1 2 lappend new 3 "4 5" => 1 2 3 {4 5} set new => 1 2 3 {4 5} The lappend command is unique among the list-related commands because its first argument is the name of a list-valued variable, while all the other commands take list values as arguments. You can call lappend with the name of an undefined variable and the variable will be created. The lset Command The lset command was introduced in Tcl 8.4 to make it easier, and more efficient, to set one element of a list or nested list. Like lappend, the first argument to lset is the name of a list variable. The last argument is the value to set. The middle arguments, if any, specify which element to set. If no index is specified, the whole variable is set to the new value. If the index is a single integer, or end-integer, then that element of the list is set. If you have a nested list, then you can specify several indices, and each one navigates into the nested list structure. This is illustrated in Example 5-3. If you specify several indices they can be separate arguments, or grouped into a list. Range checking in lset is strict and an error will be thrown for indices given outside of the list or sublist range. The new value of the list in the variable is returned, although you rarely need this because lset modifies the list variable directly. Example 5-3 Using lset to set an element of a list . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks lset new "a b c" => a b c lset new 1 "d e" => a {d e} c lset new 1 0 "g h" => a {{g h} e} c The concat Command The concat command is useful for splicing lists together. It works by concatenating its arguments, separating them with spaces. This joins multiple lists into one list where the top-level list elements in each input list become top-level list elements in the resulting list: Example 5-4 Using concat to splice lists together set x {4 5 6} set y {2 3} set z 1 concat $z $y $x => 1 2 3 4 5 6 Double quotes behave much like the concat command. In simple cases, double quotes behave exactly likeconcat. However, the concat command trims extra white space from the end of its arguments before joining them together with a single separating space character. Example 5-5 compares the use of list, concat, and double quotes: Example 5-5 Double quotes compared to the concat and list commands set x {1 2} => 1 2 set y "$x 3" => 1 2 3 set y [concat $x 3] => 1 2 3 set s { 2 } => 2 set y "1 $s 3" => 1 2 3 set y [concat 1 $s 3] => 1 2 3 set z [list $x $s 3] => {1 2} { 2 } 3 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The distinction between list and concat becomes important when Tcl commands are built dynamically. The basic rule is thatlist and lappend preserve list structure, while concat (or double quotes) eliminates one level of list structure. The distinction can be subtle because there are examples where list and concat return the same results. Unfortunately, this can lead to data-dependent bugs. Throughout the examples of this book, you will see the list command used to safely construct lists. This issue is discussed more inChapter 10. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Getting List Elements: , , and llength lindex lrange The llength command returns the number of elements in a list. llength {a b {c d} "e f g" h} => 5 llength {} => 0 The lindex command returns a particular element of a list. It takes an index; list indices count from zero. set x {1 2 3} lindex $x 1 => 2 You can use the keyword end to specify the last element of a list, or the syntaxend-N to count back from the end of the list. The following commands are equivalent ways to get the element just before the last element in a list. lindex $list [expr {[llength $list] - 2}] lindex $list end-1 The lrange command returns a range of list elements. It takes a list and two indices as arguments. Again,end or end-N can be used as an index: lrange {1 2 3 {4 5}} 2 end => 3 {4 5} [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Modifying Lists: linsert and lreplace The linsert command inserts elements into a list value at a specified index. If the index is zero or less, then the elements are added to the front. If the index is equal to or greater than the length of the list, then the elements are appended to the end. Otherwise, the elements are inserted before the element that is currently at the specified index. The following command adds to the front of a list: linsert {1 2} 0 new stuff => new stuff 1 2 lreplace replaces a range of list elements with new elements. If you don't specify any new elements, you effectively delete elements from a list. Note: linsert and lreplace do not modify an existing list like thelappend and lset commands. Instead, they return a new list value. In theExample 5-6, the lreplace command does not change the value ofx: Example 5-6 Modifying lists with lreplace set x [list a {b c} e d] => a {b c} e d lreplace $x 1 2 B C => a B C d lreplace $x 0 0 => {b c} e d [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Searching Lists: lsearch lsearch returns the index of a value in the list, or -1 if it is not present.lsearch supports pattern matching in its search. Simple pattern matching is the default, and this can be disabled with the -exact option. The glob pattern matching lsearch uses is described in more detail on page 53. The -regexp option lets you specify the list value with a regular expression. Regular expressions are described inChapter 11. In the following example, the glob pattern l* matches the value list, and lsearch returns the index of that element in the input list: lsearch {here is a list} l* => 3 Example 5-7 shows ldelete as a combination of lreplace and lsearch: Example 5-7 Deleting a list element by value proc ldelete { list value } { set ix [lsearch -exact $list $value] if {$ix >= 0} { return [lreplace $list $ix $ix] } else { return $list } } Tcl 8.4 added several features to lsearch, including typed searching, optimized searches for sorted lists, and the ability to find all matching elements of a list. The lsearch typed searches use the internal object representation for efficiency and speed. For example, if you have a list of numbers, the -integer option tells lsearch to leave the values in their native integer format. Otherwise it would convert them to strings as it did the search. If your list has been sorted, the -sorted option tells lsearch to perform an efficient binary search.Sorting lists is described on page 70. The -inline option returns the list value instead of the index. This is most useful when you are matching a pattern, and it works well with the -all option that returns all matching indices, or values: set foo {the quick brown fox jumped over a lazy dog} lsearch -inline -all $foo *o* => brown fox over dog The lsearch options are described in Table 5-2: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 5-2. Options to the lsearch command -all Search for all items that match and return a list of matching indices. -ascii The list elements are to be compared as ascii strings. Only meaningful when used with-exact or -sorted. -decreasing Assume list elements are in decreasing order. Only meaningful when used with-sorted. -dictionary The list elements are to be compared using dictionary-style comparison. Only meaningful when used with -exact or -sorted. -exact Do exact string matching. Mutually exclusive with-glob and -regexp. -glob Do glob-style pattern matching (default). Mutually exclusive with-exact and -regexp. -increasing Assume list elements are in increasing order. Only meaning when used with sorted. - -inline Return the actual matching element(s) instead of the index to the element. An empty string is returned if no elements match. -integer The list elements are to be compared as integers. Only meaning when used with-exact or -sorted. -not Negate the sense of the match. -real Examine all elements as real (floating-point) values. Only meaning when used with-exact or -sorted. -regexp Do regular expression pattern matching. Mutually exclusive with -exact and -glob. Regular expressions are described in Chapter 11. -sorted Specifies that the list is presorted, so Tcl can do a faster binary search to find the pattern. -start ix Specify the start index in the list to begin searching. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Sorting Lists: lsort You can sort a list in a variety of ways with lsort. The list is not sorted in place. Instead, a new list value is returned. The basic types of sorts are specified with the -ascii, -dictionary, -integer, or -real options. The -increasing or -decreasing option indicate the sorting order. The default option set is -ascii -increasing. An ASCII sort uses character codes, and a dictionary sort folds together case and treats digits like numbers. For example: lsort -ascii {a Z n2 n100} => Z a n100 n2 lsort -dictionary {a Z n2 n100} => a n2 n100 Z You can provide your own sorting function for special-purpose sorting. For example, suppose you have a list of names, where each element is itself a list containing the person's first name, middle name (if any), and last name. The default sorts by everyone's first name. If you want to sort by their last name, you need to supply a sorting command. Example 5-8 Sorting a list using a comparison function proc NameCompare {a b} { set alast [lindex $a end] set blast [lindex $b end] set res [string compare $alast $blast] if {$res != 0} { return $res } else { return [string compare $a $b] } } set list {{Brent B. Welch} {John Ousterhout} {Miles Davis}} => {Brent B. Welch} {John Ousterhout} {Miles Davis} lsort -command NameCompare $list => {Miles Davis} {John Ousterhout} {Brent B. Welch} The NameCompare procedure extracts the last element from each of its arguments and compares those. If they are equal, then it just compares the whole of each argument. Tcl 8.0 added a -index option to lsort that can be used to sort lists on an index. Instead of usingNameCompare, you could do this: lsort -index end $list Tcl 8.3 added a -unique option that removes duplicates during sort: lsort -unique {a b a z c b} => a b c z [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks The split Command The split command takes a string and turns it into a list by breaking it at specified characters and ensuring that the result has the proper list syntax. The split command provides a robust way to turn input lines into proper Tcl lists: set line {welch:*:28405:100:Brent Welch:/usr/welch:/bin/csh} split $line : => welch * 28405 100 {Brent Welch} /usr/welch /bin/csh lindex [split $line :] 4 => Brent Welch Do not use list operations on arbitrary data. Even if your data has space-separated words, you should be careful when using list operators on arbitrary input data. Otherwise, stray double quotes or curly braces in the input can result in invalid list structure and errors in your script. Your code will work with simple test cases, but when invalid list syntax appears in the input, your script will raise an error. The next example shows what happens when input is not a valid list. The syntax error, an unmatched quote, occurs in the middle of the list. However, you cannot access any of the list because the lindex command tries to convert the value to a list before returning any part of it. Example 5-9 Use split to turn input data into Tcl lists set line {this is "not a tcl list} lindex $line 1 => unmatched open quote in list lindex [split $line] 2 => "not The default separator character for split is white space, which contains spaces, tabs, and newlines. If there are multiple separator characters in a row, these result in empty list elements; the separators are not collapsed. The following command splits on commas, periods, spaces, and tabs. The backslash–space sequence is used to include a space in the set of characters. You could also group the argument to split with double quotes: set line "\tHello, world." split $line \ ,.\t => {} Hello {} world {} A trick that splits each character into a list element is to specify an empty string as the split character. This lets you get at individual characters with list operations: split abc {} => a b c This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks However, if you write scripts that process data one character at a time, they may run slowly. Read Chapter 11 about regular expressions for hints on really efficient string processing and using regexp for a multi-character split routine. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than [ Team LiB ] The join Command The join command is the inverse of split. It takes a list value and reformats it with specified characters separating the list elements. In doing so, it removes any curly braces from the string representation of the list that are used to group the top-level elements. For example: join {1 {2 3} {4 5 6}} : => 1:2 3:4 5 6 If the treatment of braces is puzzling, remember that the first value is parsed into a list. The braces around element values disappear in the process. Example 5-10 shows a way to implement join in a Tcl procedure, which may help to understand the process: Example 5-10 Implementing join in Tcl proc join {list sep} { set s {} ;# s is the current separator set result {} foreach x $list { append result $s $x set s $sep } return $result } [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Related Chapters Arrays are the other main data structure in Tcl. They are described inChapter 8. List operations are used when generating Tcl code dynamically. Chapter 10 describes these techniques when using theeval command. The foreach command loops over the values in a list. It is described on page 79 inChapter 6. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 6. Control Structure Commands This chapter describes the Tcl commands that implement control structures: if, switch, foreach, while, for, break, continue, catch, error, and return. Control structure in Tcl is achieved with commands, just like everything else. There are looping commands: while, foreach, and for. There are conditional commands: if and switch. There is an error handling command: catch. Finally, there are some commands to fine-tune control structures: break, continue, return, and error. A control structure command often has a command body that is executed later, either conditionally or in a loop. In this case, it is important to group the command body with curly braces to avoid substitutions at the time the control structure command is invoked. Group with braces, and let the control structure command trigger evaluation at the proper time. A control structure command returns the value of the last command it chose to execute. Another pleasant property of curly braces is that they group things together while including newlines. The examples use braces in a way that is both readable and convenient for extending the control structure commands across multiple lines. Commands like if, for, and while involve boolean expressions. They use the expr command internally, so there is no need for you to invokeexpr explicitly to evaluate their boolean test expressions. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] If Then Else The if command is the basic conditional command. If an expression is true, then execute one command body; otherwise, execute another command body. The second command body (the else clause) is optional. The syntax of the command is: if expression ?then? body1 ?else? ?body2? The then and else keywords are optional. In practice, I omitthen but use else as illustrated in the next example. I always use braces around the command bodies, even in the simplest cases: Example 6-1 A conditional if then else command if {$x == 0} { puts stderr "Divide by zero!" } else { set slope [expr $y/$x] } Curly brace positioning is important. The style of this example takes advantage of the way the Tcl interpreter parses commands. Recall that newlines are command terminators, except when the interpreter is in the middle of a group defined by braces or double quotes. The stylized placement of the opening curly brace at the end of the first and third lines exploits this property to extend the if command over multiple lines. The first argument to if is a boolean expression. As a matter of style this expression is grouped with curly braces. The expression evaluator performs variable and command substitution on the expression. Using curly braces ensures that these substitutions are performed at the proper time. It is possible to be lax in this regard, with constructs such as: if $x break continue This is a sloppy, albeit legitimate, if command that will either break out of a loop or continue with the next iteration depending on the value of variable x. This style is fragile and error prone. Instead, always use braces around the command bodies to avoid trouble later when you modify the command. The following is much better (use then if it suits your taste): if {$x} { break } else { continue This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } When you are testing the result of a command, you can get away without using curly braces around the command, like this: if [command] body1 However, it turns out that you can execute theif statement more efficiently if you always group the expression with braces, like this: if {[command]} body1 You can create chained conditionals by using the elseif keyword. Again, note the careful placement of curly braces that create a singleif command: Example 6-2 Chained conditional with elseif if {$key < 0} { incr range 1 } elseif {$key == 0} { return $range } else { incr range -1 } Any number of conditionals can be chained in this manner. However, the switch command provides a more powerful way to test multiple conditions. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Switch The switch command is used to branch to one of many command bodies depending on the value of an expression. The choice can be made on the basis of pattern matching as well as simple comparisons. Pattern matching is discussed in more detail in Chapter 4 and Chapter 11. The general form of the command is: switch flags value pat1 body1 pat2 body2 ... Any number of pattern-body pairs can be specified. If multiple patterns match, only the body of the first matching pattern is evaluated. You can also group all the pattern-body pairs into one argument: switch flags value { pat1 body1 pat2 body2 ... } The first form allows substitutions on the patterns but will require backslashes to continue the command onto multiple lines. This is shown in Example 6-4 on page 78. The second form groups all the patterns and bodies into one argument. This makes it easy to group the whole command without worrying about newlines, but it suppresses any substitutions on the patterns. This is shown in Example 6-3. In either case, you should always group the command bodies with curly braces so that substitution occurs only on the body with the pattern that matches the value. There are four possible flags that determine how value is matched. -exact Matches the value exactly to one of the patterns. This is the default. -glob Uses glob-style pattern matching. See page 53. -regexp Uses regular expression pattern matching. See page 144. -- No flag (or end of flags). Necessary when value can begin with -. The switch command raises an error if any other flag is specified or if thevalue begins with -. In practice I always use the-- flag before value so that I don't have to worry about that problem. If the pattern associated with the last body is default, then this command body is executed if no other patterns match. Thedefault keyword works only on the last pattern-body pair. If you use the default pattern on an earlier body, it will be treated as a pattern to match the literal string default: Example 6-3 Using switch for an exact match switch -exact -- $value { foo { doFoo; incr count(foo) } bar { doBar; return $count(foo)} default { incr count(other) } } If you have variable references or backslash sequences in the patterns, then you cannot use braces around all the pattern-body pairs. You must use backslashes to escape the newlines in the command: Example 6-4 Using switch with substitutions in the patterns . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks switch -regexp -- $value \ ^$key { body1 }\ \t### { body2 }\ {[0-9]*} { body3 } In this example, the first and second patterns have substitutions performed to replace $key with its value and \t with a tab character. The third pattern is quoted with curly braces to prevent command substitution; square brackets are part of the regular expression syntax, too. (See page Chapter 11.) If the body associated with a pattern is just a dash, -, then the switch command "falls through" to the body associated with the next pattern. You can tie together any number of patterns in this manner. Example 6-5 A switch with "fall through" cases switch -glob -- $value { X* Y* { takeXorYaction $value } } Comments in switch Commands A comment can occur only where the Tcl parser expects a command to begin. This restricts the location of comments in a switch command. You must put them inside the command body associated with a pattern, as shown in Example 6-6. If you put a comment at the same level as the patterns, theswitch command will try to interpret the comment as one or more pattern-body pairs. Example 6-6 Comments in switch commands switch -- $value { # this comment confuses switch pattern { # this comment is ok } } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] While The while command takes two arguments, a test and a command body: while booleanExpr body The while command repeatedly tests the boolean expression and then executes the body if the expression is true (nonzero). Because the test expression is evaluated again before each iteration of the loop, it is crucial to protect the expression from any substitutions before the while command is invoked. The following is an infinite loop (see also Example 1-13 on page 12): set i 0 ; while $i<10 {incr i} The following behaves as expected: set i 0 ; while {$i<10} {incr i} It is also possible to put nested commands in the boolean expression. The following example uses gets to read standard input. The gets command returns the number of characters read, returning -1 upon end of file. Each time through the loop, the variable line contains the next line in the file: Example 6-7 A while loop to read standard input set numLines 0 ; set numChars 0 while {[gets stdin line] >= 0} { incr numLines incr numChars [string length $line] } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Foreach The foreach command loops over a command body assigning one or more loop variables to each of the values in one or more lists. Multiple loop variables, which were introduced in Tcl 7.5, are a very useful feature. The syntax for the simple case of a single variable and a single list is: foreach loopVar valueList commandBody The first argument is the name of a variable, and the command body is executed once for each element in the list with the loop variable taking on successive values in the list. The list can be entered explicitly, as in the next example: Example 6-8 Looping with foreach set i 1 foreach value {1 3 5 7 11 13 17 19 23} { set i [expr $i*$value] } set i => 111546435 It is also common to use a list-valued variable or command result instead of a static list value. The next example loops through command-line arguments. The variable argv is set by the Tcl interpreter to be a list of the command-line arguments given when the interpreter was started: Example 6-9 Parsing command-line arguments # argv is set by the Tcl shells # possible flags are: # -max integer # -force # -verbose set state flag set force 0 set verbose 0 set max 10 foreach arg $argv { switch -- $state { flag { switch -glob -- $arg { -f* {set force 1} -v* {set verbose 1} -max {set state max} default {error "unknown flag $arg"} } } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks max { set max $arg set state flag } } } The loop uses the state variable to keep track of what is expected next, which in this example is either a flag or the integer value for -max. The -- flag to switch is required in this example because theswitch command complains about a bad flag if the pattern begins with a- character. The -glob option lets the user abbreviate the-force and -verbose options. If the list of values is to contain variable values or command results, then thelist command should be used to form the list. Avoid double quotes because if any values or command results contain spaces or braces, the list structure will be reparsed, which can lead to errors or unexpected results. Example 6-10 Using list with foreach foreach x [list $a $b [foo]] { puts stdout "x = $x" } The loop variable x will take on the value of a, the value of b, and the result of thefoo command, regardless of any special characters or whitespace in those values. Multiple Loop Variables You can have more than one loop variable with foreach. Suppose you have two loop variablesx and y. In the first iteration of the loop,x gets the first value from the value list and y gets the second value. In the second iteration, x gets the third value andy gets the fourth value. This continues until there are no more values. If there are not enough values to assign to all the loop variables, the extra variables get the empty string as their value. Example 6-11 Multiple loop variables with foreach foreach {key value} {orange 55 blue 72 red 24 green} { puts "$key: $value" } orange: 55 blue: 72 red: 24 green: If you have a command that returns a short list of values, then you can abuse the foreach command to assign the results of the commands to several variables all at once. For example, suppose the command MinMax returns two values as a list: the minimum and maximum values. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Here is one way to get the values: set result [MinMax $list] set min [lindex $result 0] set max [lindex $result 1] The foreach command lets us do this much more compactly: foreach {min max} [MinMax $list] {break} The break in the body of the foreach loop guards against the case where the command returns more values than we expected. This trick is encapsulated into the lassign procedure in Example 10-4 on page 139. Multiple Value Lists The foreach command has the ability to loop over multiple value lists in parallel. In this case, each value list can also have one or more variables. The foreach command keeps iterating until all values are used from all value lists. If a value list runs out of values before the last iteration of the loop, its corresponding loop variables just get the empty string for their value. Example 6-12 Multiple value lists with foreach foreach {k1 k2} {orange blue red green black} value {55 72 24} { puts "$k1 $k2: $value" } orange blue: 55 red green: 72 black : 24 [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] For The for command is similar to the C for statement. It takes four arguments: for initial test final body The first argument is a command to initialize the loop. The second argument is a boolean expression that determines whether the loop body will execute. The third argument is a command to execute after the loop body: Example 6-13 A for loop for {set i 0} {$i < 10} {incr i 3} { lappend aList $i } set aList => 0 3 6 9 You could use for to iterate over a list, but you should really useforeach instead. Code like the following is slow and cluttered: for {set i 0} {$i < [llength $list]} {incr i} { set value [lindex $list $i] } This is the same as: foreach value $list { } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Break and Continue You can control loop execution with the break and continue commands. The break command causes immediate exit from a loop, while the continue command causes the loop to continue with the next iteration. There is nogoto command in Tcl. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Catch Until now we have ignored the possibility of errors. In practice, however, a command will raise an error if it is called with the wrong number of [*] arguments, or if it detects some error condition particular to its implementation. An uncaught error aborts execution of a script. The catch command is used to trap such errors. It takes two arguments: [*] More precisely, the Tcl script unwinds and the currentTcl_Eval procedure in the C runtime library returns TCL_ERROR. There are three cases. In interactive use, the Tcl shell prints the error message. In Tk, errors that arise during event handling trigger a call to bgerror, a Tcl procedure you can implement in your application. In your own C code, you should check the result of Tcl_Eval and take appropriate action in the case of an error. catch command ?resultVar? The first argument to catch is a command body. The second argument is the name of a variable that will contain the result of the command, or an error message if the command raises an error. catch returns zero if there was no error caught, or a nonzero error code if it did catch an error. You should use curly braces to group the command instead of double quotes because catch invokes the full Tcl interpreter on the command. If double quotes are used, an extra round of substitutions occurs before catch is even called. The simplest use of catch looks like the following: catch { command } A more careful catch phrase saves the result and prints an error message: Example 6-14 A standard catch phrase if {[catch { command arg1 arg2 ... } result]} { puts stderr $result } else { # command was ok, result contains the return value } A more general catch phrase is shown in the next example. Multiple commands are grouped into a command body. The errorInfo variable is set by the Tcl interpreter after an error to reflect the stack trace from the point of the error: Example 6-15 A longer catch phrase if {[catch { command1 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks command2 command3 } result]} { global errorInfo puts stderr $result puts stderr "*** Tcl TRACE ***" puts stderr $errorInfo } else { # command body ok, result of last command is in result } These examples have not grouped the call to catch with curly braces. This is acceptable because catch always returns an integer, so theif command will parse correctly. However, if we had used while instead of if, then curly braces would be necessary to ensure that thecatch phrase was evaluated repeatedly. Catching More Than Errors The catch command catches more than just errors. If the command body containsreturn, break, or continue commands, these terminate the command body and are reflected by catch as nonzero return codes. You need to be aware of this if you try to isolate troublesome code with a catch phrase. An innocent looking return command will cause the catch to signal an apparent error. The next example usesswitch to find out exactly what catch returns. Nonerror cases are passed up to the surrounding code by invokingreturn, break, or continue: Example 6-16 There are several possible return values from catch switch [catch { command1 command2 ... } result] { 0{ # Normal completion } 1{ # Error case } 2 { return $result ;# return from procedure} 3 { break ;# break out of the loop} 4 { continue ;# continue loop} default { # User-defined error codes } } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] Error The error command raises an error condition that terminates a script unless it is trapped with thecatch command. The command takes up to three arguments: error message ?info? ?code? The message becomes the error message stored in the result variable of thecatch command. If the info argument is provided, then the Tcl interpreter uses this to initialize theerrorInfo global variable. That variable is used to collect a stack trace from the point of the error. If the info argument is not provided, then the error command itself is used to initialize theerrorInfo trace. Example 6-17 Raising an error proc foo {} { error bogus } foo => bogus set errorInfo => bogus while executing "error bogus" (procedure "foo" line 2) invoked from within "foo" In the previous example, the error command itself appears in the trace. One common use of theinfo argument is to preserve the errorInfo that is available after a catch. In the next example, the information from the original error is preserved: Example 6-18 Preserving errorInfo when calling error if {[catch {foo} result]} { global errorInfo set savedInfo $errorInfo # Attempt to handle the error here, but cannot... error $result $savedInfo } The code argument specifies a concise, machine-readable description of the error. It is stored into the globalerrorCode variable. It defaults to NONE. Many of the file system commands return anerrorCode that has three elements: POSIX, the error name (e.g., ENOENT), and the associated error message: POSIX ENOENT {No such file or directory} This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks In addition, your application can define error codes of its own. Catch phrases can examine the code in the global errorCode variable and decide how to respond to the error. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Return The return command is used to return from a procedure. It is needed if return is to occur before the end of the procedure body, or if a constant value needs to be returned. As a matter of style, I also use return at the end of a procedure, even though a procedure returns the value of the last command executed in the body. Exceptional return conditions can be specified with some optional arguments toreturn. The complete syntax is: return ?-code c? ?-errorinfo i? ?-errorcode ec? string The -code option value is one of ok, error, return, break, continue, or an integer. ok is the default if -code is not specified. The -code error option makes return behave much like theerror command. The -errorcode option sets the global errorCode variable, and the -errorinfo option initializes the errorInfo global variable. When you use return -code error, there is no error command in the stack trace. Compare Example 6-17 with Example 6-19: Example 6-19 Raising an error with return proc bar {} { return -code error bogus } catch {bar} result => 1 set result => bogus set errorInfo => bogus while executing "bar" The return, break, and continue code options take effect in the caller of the procedure doing the exceptional return. If -code return is specified, then the calling procedure returns. If -code break is specified, then the calling procedure breaks out of a loop, and if-code continue is specified, then the calling procedure continues to the next iteration of the loop. These -code options to return enable the construction of new control structures entirely in Tcl. The following example implements the break command with a Tcl procedure: proc break {} { return -code break } You can return integer-valued codes of your own with return -code, and trap them with catch in order to create your own control structures. There are also a number of exception packages available on the net that provide Java-like try-catch-except structures for Tcl, although the Tcl exception mechanism strikes a nice balance between simplicity and power. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 7. Procedures and Scope Procedures encapsulate a set of commands, and they introduce a local scope for variables. Commands described are:proc, global, and upvar. Procedures parameterize a commonly used sequence of commands. In addition, each procedure has a new local scope for variables. The scope of a variable is the range of commands over which it is defined. Originally, Tcl had one global scope for shared variables, local scopes within procedures, and one global scope for procedures. Tcl 8.0 added namespaces that provide new scopes for procedures and global variables. For simple applications you can ignore namespaces and just use the global scope. Namespaces are described in Chapter 14. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] The proc Command A Tcl procedure is defined with the proc command. It takes three arguments: proc name params body The first argument is the procedure name, which is added to the set of commands understood by the Tcl interpreter. The name is case sensitive and can contain any characters. Procedure names do not conflict with variable names. The second argument is a list of parameter names. The last argument is the body of the procedure. Once defined, a Tcl procedure is used just like any other Tcl command. When it is called, each argument is assigned to the corresponding parameter and the body is evaluated. The result of the procedure is the result returned by the last command in the body. The return command can be used to return a specific value. Procedures can have default parameters so that the caller can leave out some of the command arguments. A default parameter is specified with its name and default value, as shown in the next example: Example 7-1 Default parameter values proc P2 {a {b 7} {c -2} } { expr $a / $b + $c } P2 6 3 => 0 Here the procedure P2 can be called with one, two, or three arguments. If it is called with only one argument, then the parameters b and c take on the values specified in the proc command. If two arguments are provided, then onlyc gets the default value, and the arguments are assigned to a and b. At least one argument and no more than three arguments can be passed toP2. A procedure can take a variable number of arguments by specifying the args keyword as the last parameter. When the procedure is called, the args parameter is a list that contains all the remaining values: Example 7-2 Variable number of arguments proc ArgTest {a {b foo} args} { foreach param {a b args} { puts stdout "\t$param = [set $param]" } } set x one set y {two things} This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set z \[special\$ ArgTest $x => a = one b = foo args = ArgTest $y $z => a = two things b = [special$ args = ArgTest $x $y $z => a = one b = two things args = {[special$} ArgTest $z $y $z $x => a = [special$ b = two things args = {[special$} one The effect of the list structure in args is illustrated by the treatment of variable z in Example 7-2. The value of z has special characters in it. When $z is passed as the value of parameterb, its value comes through to the procedure unchanged. When$z is part of the optional parameters, quoting is automatically added to create a valid Tcl list as the value of args. Example 10-3 on page 136 illustrates a technique that uses eval to undo the effect of the added list structure. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Changing Command Names with rename The rename command changes the name of a command. There are two main uses forrename. The first is to augment an existing procedure. Before you redefine it with proc, rename the existing command: rename foo foo.orig From within the new implementation of foo you can invoke the original command asfoo.orig. Existing users of foo will transparently use the new version. The other thing you can do with rename is completely remove a command by renaming it to the empty string. For example, you might not want users to execute UNIX programs, so you could disableexec with the following command: rename exec {} Command renaming and deletion can be traced with thetrace command described in Chapter 13. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Scope By default there is a single, global scope for procedure names. This means that you can use a procedure anywhere in your script. Variables defined outside any procedure are global variables. However, as described below, global variables are not automatically visible inside procedures. There is a different namespace for variables and procedures, so you could have a procedure and a global variable with the same name without conflict. You can use the namespace facility described in Chapter 7 to manage procedures and global variables. Each procedure has a local scope for variables. That is, variables introduced in the procedure live only for the duration of the procedure call. After the procedure returns, those variables are undefined. Variables defined outside the procedure are not visible to a procedure unless the upvar or global scope commands are used. You can also use qualified names to name variables in a namespace scope. The global and upvar commands are described later in this chapter. Qualified names are described on page 208. If the same variable name exists in an outer scope, it is unaffected by the use of that variable name inside a procedure. In Example 7-3, the variable a in the global scope is different from the parametera to P1. Similarly, the global variableb is different from the variable b inside P1: Example 7-3 Variable scope and Tcl procedures set a 5 set b -8 proc P1 {a} { set b 42 if {$a < 0} { return $b } else { return $a } } P1 $b => 42 P1 [expr {$a*2}] => 10 [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The global Command Global scope is the toplevel scope. This scope is outside of any procedure. Variables defined at the global scope must be made accessible to the commands inside a procedure by using the global command. The syntax for global is: global varName1 varName2 ... The global command goes inside a procedure. The global command adds a global variable to the current scope. A common mistake is to have a single global command and expect that to apply to all procedures. However, a global command in the global scope has no effect. Instead, you must put aglobal command in all procedures that access the global variable. The variable can be undefined at the time the global command is used. When the variable is defined, it becomes visible in the global scope. Example 7-4 shows a random number generator. Before we look at the example, let me point out that the best way to get random numbers in Tcl is to use the rand() math function: expr rand() => .137287362934 The point of the example is to show a state variable, the seed, that has to persist between calls to random, so it is kept in a global variable. The choice of randomSeed as the name of the global variable associates it with the random number generator. It is important to pick names of global variables carefully to avoid conflict with other parts of your program. For comparison, Example 14-1 on page 206 uses namespaces to hide the state variable: [*] Example 7-4 A random number generator. proc RandomInit { seed } { global randomSeed set randomSeed $seed } proc Random {} { global randomSeed set randomSeed [expr ($randomSeed*9301 + 49297) % 233280] return [expr $randomSeed/double(233280)] } proc RandomRange { range } { This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks expr int([Random]*$range) } RandomInit [pid] => 5049 Random => 0.517686899863 Random => 0.217176783265 RandomRange 100 => 17 [*] Adapted from Exploring Expect by Don Libes, O'Reilly & Associates, Inc., 1995, and fromNumerical Recipes in C by Press et al., Cambridge University Press, 1988. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Call by Name Using upvar Use the upvar command when you need to pass the name of a variable, as opposed to its value, into a procedure. Theupvar command associates a local variable with a variable in a scope up the Tcl call stack. The syntax of the upvar command is: upvar ?level? varName localvar The level argument is optional, and it defaults to 1, which means one level up the Tcl call stack. You can specify some other number of frames to go up, or you can specify an absolute frame number with a #number syntax. Level #0 is the global scope, so theglobal foo command is equivalent to: upvar #0 foo foo The variable in the uplevel stack frame can be either a scalar variable, an array element, or an array name. In the first two cases, the local variable is treated like a scalar variable. In the case of an array name, then the local variable is treated like an array. The use of upvar and arrays is discussed further in Chapter 8 on page 99. The following procedure uses upvar to print the value of a variable given its name. Example 7-5 Print variable by name proc PrintByName { varName } { upvar 1 $varName var puts stdout "$varName = $var" } You can use upvar to fix the incr command. One drawback of the built-inincr is that it raises an error if the variable does not exist. We can define a new version of incr that initializes the variable if it does not already exist: Example 7-6 Improved incr procedure proc incr { varName {amount 1}} { upvar 1 $varName var if {[info exists var]} { set var [expr $var + $amount] } else { set var $amount } return $var } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Variable Aliases with upvar The upvar command is useful in any situation where you have the name of a variable stored in another variable. InExample 7-2 on page 88, the loop variable param holds the names of other variables. Their value is obtained with this construct: puts stdout "\t$param = [set $param]" Another way to do this is to use upvar. It eliminates the need to use awkward constructs like[set $param]. If the variable is in the same scope, use zero as the scope number with upvar. The following is equivalent: upvar 0 $param x puts stdout "\t$param = $x" Associating State with Data Suppose you have a program that maintains state about a set of objects like files, URLs, or people. You can use the name of these objects as the name of a variable that keeps state about the object. The upvar command makes this more convenient: upvar #0 $name state Using the name directly like this is somewhat risky. If there were an object named x, then this trick might conflict with an unrelated variable named x elsewhere in your program. You can modify the name to make this trick more robust: upvar #0 state$name state Your code can pass name around as a handle on an object, then useupvar to get access to the data associated with the object. Your code is just written to use the state variable, which is an alias to the state variable for the current object. This technique is illustrated inExample 17-7 on page 245. Namespaces and upvar You can use upvar to create aliases for namespace variables, too. Namespaces are described inChapter 14. For example, as an alternative to reserving all global variables beginning with state, you can use a namespace to hide these variables: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks upvar #0 state::$name state Now state is an alias to the namespace variable. Thisupvar trick works from inside any namespace. Commands That Take Variable Names Several Tcl commands involve variable names. For example, the Tk widgets can be associated with a global Tcl variable. The vwait and tkwait commands also take variable names as arguments. Upvar aliases do not work with Tk widget text variables. The aliases created with upvar do not work with these commands, nor do they work if you usetrace, which is described on page 193. Instead, you must use the actual name of the global variable. To continue the above example where state is an alias, you cannot: vwait state(foo) button .b -textvariable state(foo) Instead, you must vwait state$name\(foo) button .b -textvariable state$name\(foo) The backslash turns off the array reference so Tcl does not try to access name as an array. You do not need to worry about special characters in $name, except parentheses. Once the name has been passed into the Tk widget it will be used directly as a variable name. Text variables for labels are explained on page 490, and text variables for entry widgets are illustrated in Example 34-1 on page 508. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 8. Tcl Arrays This chapter describes Tcl arrays, which provide a flexible mechanism to build many other data structures in Tcl. Tcl command described is: array. An array is a Tcl variable with a string-valued index. You can think of the index as a key, and the array as a collection of related data items identified by different keys. The index, or key, can be any string value. Internally, an array is implemented with a hash table, so the cost of accessing each array element is about the same. Before Tcl 8.0, arrays had a performance advantage over lists that took time to access proportional to the size of the list. The flexibility of arrays makes them an important tool for the Tcl programmer. A common use of arrays is to manage a collection of variables, much as you use a C struct or Pascal record. This chapter shows how to create several simple data structures using Tcl arrays. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Array Syntax The index of an array is delimited by parentheses. The index can have any string value, and it can be the result of variable or command substitution. Array elements are defined with set: set arr(index) value The value of an array element is obtained with $ substitution: set foo $arr(index) Example 8-1 uses the loop variable value $i as an array index. It setsarr(x) to the product of 1 * 2 * ... * x: Example 8-1 Using arrays set arr(0) 1 for {set i 1} {$i <= 10} {incr i} { set arr($i) [expr {$i * $arr([expr {$i-1}])}] } Complex Indices An array index can be any string, like orange, 5, 3.1415, or foo,bar. The examples in this chapter, and in this book, often use indices that are pretty complex strings to create flexible data structures. As a rule of thumb, you can use any string for an index, but avoid using a string that contains spaces. Parentheses are not a grouping mechanism. The main Tcl parser does not know about array syntax. All the rules about grouping and substitution described in Chapter 1 are still the same in spite of the array syntax described here. Parentheses do not group like curly braces or quotes, which is why a space causes problems. If you have complex indices, use a comma to separate different parts of the index. If you use a space in an index instead, then you have a This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks quoting problem. The space in the index needs to be quoted with a backslash, or the whole variable reference needs to be grouped: set {arr(I'm asking for trouble)} {I told you so.} set arr(I'm\ asking\ for\ trouble) {I told you so.} If the array index is stored in a variable, then there is no problem with spaces in the variable's value. The following works well: set index {I'm asking for trouble} set arr($index) {I told you so.} Array Variables You can use an array element as you would a simple variable. For example, you can test for its existence with info exists, increment its value with incr, and append elements to it withlappend: if {[info exists stats($event)]} {incr stats($event)} You can delete an entire array, or just a single array element with unset. Using unset on an array is a convenient way to clear out a big data structure. It is an error to use a variable as both an array and a normal variable. The following is an error: set arr(0) 1 set arr 3 => can't set "arr": variable is array The name of the array can be the result of a substitution. This is a tricky situation, as shown in Example 8-2: Example 8-2 Referencing an array indirectly set name TheArray => TheArray set ${name}(xyz) {some value} => some value set x $TheArray(xyz) => some value set x ${name}(xyz) => TheArray(xyz) set x [set ${name}(xyz)] => some value A better way to deal with this situation is to use the upvar command, which is introduced on page 91. The previous example is much cleaner when upvar is used: Example 8-3 Referencing an array indirectly using upvar . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set name TheArray => TheArray upvar 0 $name a set a(xyz) {some value} => some value set x $TheArray(xyz) => some value [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The array Command The array command returns information about array variables. Thearray names command returns the index names that are defined in the array. If the array variable is not defined, then array names just returns an empty list. It allows easy iteration through an array with aforeach loop: foreach index [array names arr pattern] { # use arr($index) } The order of the names returned by array names is arbitrary. It is essentially determined by the hash table implementation of the array. You can limit what names are returned by specifying a pattern that matches indices. The pattern is the kind supported by thestring match command, which is described on page 53. It is also possible to iterate through the elements of an array one at a time using the search-related commands listed in Table 8-1. The ordering is also random, and I find the foreach over the results ofarray names much more convenient. If your array has an extremely large number of elements, or if you need to manage an iteration over a long period of time, then the array search operations might be more appropriate. Frankly, I never use them. Table 8-1 summarizes the array command: Table 8-1. The array command array exists arr Returns 1 if arr is an array variable. array get arr ?pattern? Returns a list that alternates between an index and the corresponding array value. pattern selects matching indices. If not specified, all indices and values are returned. array names arr ?mode? Returns the list of all indices defined for arr, or those that matchpattern. mode specifies the pattern type and ?pattern? may be -exact, -glob (default) or -regexp. array set arr list Initializes the array arr from list, which has the same form as the list returned by array get. array size arr Returns the number of indices defined for arr. array unset arr ?pattern? Unset elements in arr matching the specified glob-stylepattern. If not specified, unsetarr. (Tcl 8.3) array startsearch arr Returns a search token for a search througharr. array nextelement arr id Returns the value of the next element in arr in the search identified by the tokenid. Returns an empty string if no more elements remain in the search. array anymore arr id Returns 1 if more elements remain in the search. array donesearch arr id Ends the search identified by id. array statistics arr Returns statistics about the array hash table. (Tcl 8.4) This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Converting Between Arrays and Lists The array get and array set operations are used to convert between an array and a list. The list returned byarray get has an even number of elements. The first element is an index, and the next is the corresponding array value. The list elements continue to alternate between index and value. The list argument to array set must have the same structure. array set fruit { best kiwi worst peach ok banana } array get fruit => ok banana best kiwi worst peach Another way to loop through the contents of an array is to usearray get and the two-variable form of the foreach command. foreach {key value} [array get fruit] { # key is ok, best, or worst # value is some fruit } Passing Arrays by Name The upvar command works on arrays. You can pass an array name to a procedure and use theupvar command to get an indirect reference to the array variable in the caller's scope. This is illustrated in Example 8-4, which inverts an array. As witharray names, you can specify a pattern to array get to limit what part of the array is returned. This example usesupvar because the array names are passed into the ArrayInvert procedure. The inverse array does not need to exist before you callArrayInvert. Example 8-4 ArrayInvert inverts an array proc ArrayInvert {arrName inverseName {pattern *}} { upvar $arrName array $inverseName inverse foreach {index value} [array get array $pattern] { set inverse($value) $index } } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Building Data Structures with Arrays This section describes several data structures you can build with Tcl arrays. These examples are presented as procedures that implement access functions to the data structure. Wrapping up your data structures in procedures is good practice. It shields the user of your data structure from the details of its implementation. Use arrays to collect related variables. A good use for arrays is to collect together a set of related variables for a module, much as one would use a record in other languages. By collecting these together in an array that has the same name as the module, name conflicts between different modules are avoided. Also, in each of the module's procedures, a single global statement will suffice to make all the state variables visible. You can also useupvar to manage a collection of arrays, as shown in Example 8-9 on page 101. Simple Records Suppose we have a database of information about people. The following examples show three different ways to store the employee name, ID, manager, and phone number. Each example implements Emp_AddRecord that stores the values, and one example accessor function that returns information about the employee (e.g., Emp_Manager.) By using simple procedures to return fields of the record, the implementation is hidden so that you can change it more easily. Example 8-5 uses on array for each field. The name of the person is the index into each array: Example 8-5 Using arrays for records, version 1 proc Emp_AddRecord {id name manager phone} { global employeeID employeeManager \ employeePhone employeeName set employeeID($name) $id set employeeManager($name) $manager set employeePhone($name) $phone This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set employeeName($id) $name } proc Emp_Manager {name} { global employeeManager return $employeeManager($name) } The employeeName array provides a secondary key. It maps from the employee ID to the name so that the other information can be obtained if you have an ID instead of a name. Example 8-6 implements the same little database using a single array with more complex indices: Example 8-6 Using arrays for records, version 2 proc Emp_AddRecord {id name manager phone} { global employee set employee(id,$name) $id set employee(manager,$name) $manager set employee(phone,$name) $phone set employee(name,$id) $name } proc Emp_Manager {name} { global employee return $employee(manager,$name) } Example 8-7 shows the last approach. Each array element is a list of fields, and the accessor functions hide the lindex command used to pick out the right field. Here the cross referencing by ID is implement differently. If we can assume that names and IDs are distinct, we can keep the cross reference in the same array: Example 8-7 Using arrays for records, version 3 proc Emp_AddRecord {id name manager phone} { global employee set employee($name) [list $name $id $manager $phone] set employee($id) $name } proc Emp_Manager {name} { global employee return [lindex $employee($name) 2] } The difference between these three approaches is partly a matter of taste. Using a single array can be more convenient because there are fewer variables to manage. Using the lists for the fields is probably the most space efficient because there are fewer elements in the array, but maintaining the lindex offsets is tedious. In any case, you should hide the implementation in a small set of procedures. A Stack This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A stack can be implemented with either a list or an array. If you use a list, then the push and pop operations have a runtime cost that is proportional to the size of the stack. If the stack has a few elements this is fine. If there are a lot of items in a stack, you may wish to use arrays instead. Example 8-8 Using a list to implement a stack proc Push { stack value } { upvar $stack list lappend list $value } proc Pop { stack } { upvar $stack list set value [lindex $list end] set list [lrange $list 0 [expr [llength $list]-2]] return $value } In these examples, the name of the stack is a parameter, and upvar is used to convert that into the data used for the stack. The variable is a list in Example 8-8 and an array in Example 8-9. The user of the stack module does not have to know. The array implementation of a stack uses one array element to record the number of items in the stack. The other elements of the array have the stack values. The Push and Pop procedures both guard against a nonexistent array with theinfo exists command. When the first assignment to S(top) is done by Push, the array variable is created in the caller's scope. The example uses array indices in two ways. Thetop index records the depth of the stack. The other indices are numbers, so the construct $S($S(top)) is used to reference the top of the stack. Example 8-9 Using an array to implement a stack proc Push { stack value } { upvar $stack S if {![info exists S(top)]} { set S(top) 0 } set S($S(top)) $value incr S(top) } proc Pop { stack } { upvar $stack S if {![info exists S(top)]} { return {} } if {$S(top) == 0} { return {} } else { incr S(top) -1 set x $S($S(top)) unset S($S(top)) return $x } } . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A List of Arrays Suppose you have many arrays, each of which stores some data, and you want to maintain an overall ordering among the data sets. One approach is to keep a Tcl list with the name of each array in order. Example 8-10 defines RecordInsert to add an array to the list, and an iterator function, RecordIterate, that applies a script to each array in order. The iterator usesupvar to make data an alias for the current array. The script is executed with eval, which is described in detail inChapter 10. The Tcl commands in script can reference the arrays with the name data: Example 8-10 A list of arrays proc RecordAppend {listName arrayName} { upvar $listName list lappend list $arrayName } proc RecordIterate {listName script} { upvar $listName list foreach arrayName $list { upvar #0 $arrayName data eval $script } } Another way to implement this list-of-records structure is to keep references to the arrays that come before and after each record. Example 8-11 shows the insert function and the iterator function when using this approach. Once again,upvar is used to set up data as an alias for the current array in the iterator. In this case, the loop is terminated by testing for the existence of the next array. It is perfectly all right to make an alias with upvar to a nonexistent variable. It is also all right to change the target of theupvar alias. One detail that is missing from the example is the initialization of the very first record so that its next element is the empty string: Example 8-11 A list of arrays proc RecordInsert {recName afterThis} { upvar $recName record $afterThis after set record(next) $after(next) set after(next) $recName } proc RecordIterate {firstRecord body} { upvar #0 $firstRecord data while {[info exists data]} { eval $body upvar #0 $data(next) data } } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A Simple In-Memory Database Suppose you have to manage a lot of records, each of which contain a large chunk of data and one or more key values you use to look up those values. The procedure to add a record is called like this: Db_Insert keylist datablob The datablob might be a name, value list suitable for passing toarray set, or simply a large chunk of text or binary data. One implementation of Db_Insert might just be: foreach key $keylist { lappend Db($key) $datablob } The problem with this approach is that it duplicates the data chunks under each key. A better approach is to use two arrays. One stores all the data chunks under a simple ID that is generated automatically. The other array stores the association between the keys and the data chunks. Example 8-12, which uses the namespace syntax described inChapter 14, illustrates this approach. The example also shows how you can easily dump data structures by writing array set commands to a file, and then load them later with asource command: Example 8-12 A simple in-memory database namespace eval db { variable data ;# Array of data blobs variable uid 0 ;# Index into data variable index ;# Cross references into data } proc db::insert {keylist datablob} { variable data variable uid variable index set data([incr uid]) $datablob foreach key $keylist { lappend index($key) $uid } } proc db::get {key} { variable data variable index set result {} if {![info exist index($key)]} { return {} } foreach uid $index($key) { lappend result $data($uid) } return $result } proc db::save {filename} { variable uid set out [open $filename w] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks puts $out [list namespace eval db \ [list variable uid $uid]] puts $out [list array set db::data [array get db::data]] puts $out [list array set db::index [array get db::index]] close $out } proc db::load {filename} { source $filename } Alternatives to Using Arrays While Tcl arrays are flexible and general purpose, they are not always the best solution to your data structure problems. If you find yourself building elaborate data structures, you should consider implementing a C library to encapsulate the data structure and expose it to the scripting level with Tcl commands. For example, Chapter 47 implements a blob data structure in C. You can also use the SWIG code generator can quickly generate a Tcl command interface for a C API. Find out about SWIG at http://www.swig.org. The Metakit embedded database provides an efficient, easy, scriptable database for Tcl. It is more powerful than the simple "flat file" databases implemented in this Chapter, but it is not a full SQL database. It is part of Tclkit, or you can use it with the mk4tcl extension. Tclkit and Metakit are described in Chapter 22. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 9. Working with Files and Programs This chapter describes how to run programs, examine the file system, and access environment variables through the env array. Tcl commands described are: exec, file, open, close, read, write, puts, gets, flush, seek, tell, glob, pwd, cd, exit, pid, and registry. This chapter describes how to run programs and access the file system from Tcl. These commands were designed for UNIX. In Tcl 7.5 they were implemented in the Tcl ports to Windows and Macintosh. There are facilities for naming files and manipulating file names in a platform-independent way, so you can write scripts that are portable across systems. These capabilities enable your Tcl script to be a general-purpose glue that assembles other programs into a tool that is customized for your needs. Tcl 8.4 added support for 64-bit file systems, where available. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Running Programs with exec [*] The exec command runs programs from your Tcl script. For example: [*] Unlike other UNIX shellexec commands, the Tcl exec does not replace the current process with the new one. Instead, the Tcl library forks first and executes the program as a child process. set d [exec date] The standard output of the program is returned as the value of the exec command. However, if the program writes to its standard error channel or exits with a nonzero status code, then exec raises an error. If you do not care about the exit status, or you use a program that insists on writing to standard error, then you can use catch to mask the errors: catch {exec program arg arg} result The exec command supports a full set of I/O redirection and pipeline syntax. Each process normally has three I/O channels associated with it: standard input, standard output, and standard error. With I/O redirection, you can divert these I/O channels to files or to I/O channels you have opened with the Tcl open command. A pipeline is a chain of processes that have the standard output of one command hooked up to the standard input of the next command in the pipeline. Any number of programs can be linked together into a pipeline. Example 9-1 Using exec on a process pipeline set n [exec sort < /etc/passwd | uniq | wc -l 2> /dev/null] Example 9-1 uses exec to run three programs in a pipeline. The first program issort, which takes its input from the file/etc/passwd. The output of sort is piped into uniq, which suppresses duplicate lines. The output of uniq is piped into wc, which counts the lines. The error output of the command is diverted to the null device to suppress any error messages. Table 9-1 provides a summary of the syntax understood by theexec command. Table 9-1. Summary of the exec syntax for I/O redirection -keepnewline (First argument.) Do not discard trailing newline from the result. | Pipes standard output from one process into another. |& Pipes both standard output and standard error output. < fileName Takes input from the named file. <@ fileId Takes input from the I/O channel identified byfileId. << value Takes input from the given value. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks > fileName Overwrites fileName with standard output. 2> fileName Overwrites fileName with standard error output. >& fileName Overwrites fileName with both standard error and standard out. >> fileName Appends standard output to the named file. 2>> fileName Appends standard error to the named file. >>& fileName Appends both standard error and standard output to the named file. >@ fileId Directs standard output to the I/O channel identified byfileId. 2>@ fileId Directs standard error to the I/O channel identified by fileId. >&@ fileId Directs both standard error and standard output to the I/O channel. & As the last argument, indicates pipeline should run in background. A trailing & causes the program to run in the background. In this case, the process identifier is returned by theexec command. Otherwise, the exec command blocks during execution of the program, and the standard output of the program is the return value ofexec. The trailing newline in the output is trimmed off, unless you specify -keepnewline as the first argument to exec. If you look closely at the I/O redirection syntax, you'll see that it is built up from a few basic building blocks. The basic idea is that | stands for pipeline, > for output, and< for input. The standard error is joined to the standard output by&. Standard error is diverted separately by using2>. You can use your own I/O channels by using @. The auto_noexec Variable The Tcl shell programs are set up during interactive use to attempt to execute unknown Tcl commands as programs. For example, you can get a directory listing by typing: ls instead of: exec ls This is handy if you are using the Tcl interpreter as a general shell. It can also cause unexpected behavior when you are just playing around. To turn this off, define the auto_noexec variable: set auto_noexec anything Limitations of exec on Windows This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Windows 3.1 has an unfortunate combination of special cases that stem from console-mode programs, 16-bit programs, and 32-bit programs. In addition, pipes are really just simulated by writing output from one process to a temporary file and then having the next process read from that file. If exec or a process pipeline fails, it is because of a fundamental limitation of Windows. The good news is that Windows 98 and Windows NT cleaned up most of the problems with exec. Windows NT, Window 2000, and Windows XP are pretty robust. Tcl 8.0p2 was the last release to officially support Windows 3.1. That release includes Tcl1680.dll, which is necessary to work with the win32s subsystem. If you copy that file into the same directory as the other Tcl DLLs, you may be able to use some later releases of Tcl on Windows 3.1. However, Tcl 8.3 completely removed support for win32s while adding support for Windows XP-64. AppleScript on Macintosh The exec command is not provided on the Macintosh. Tcl ships with an AppleScript extension that lets you control other Macintosh applications. You can find documentation in the AppleScript.html that goes with the distribution. You must usepackage require to load the AppleScript command: package require Tclapplescript AppleScript junk => bad option "junk": must be compile, decompile, delete, execute, info, load, run, or store. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The file Command The file command provides several ways to check the status of files in the file system. For example, you can find out if a file exists, what type of file it is, and other file attributes. There are facilities for manipulating files in a platform-independent manner. Table 9-2 provides a summary of the various forms of the file command. They are described in more detail later. Note that several operations have been added since the introduction of the file command; the table indicates the version of Tcl in which they were added. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 9-2. The file command options file atime name ?time? Returns access time as a decimal string. Iftime is specified, the access time of the file is set. file attributes name ?option? ?value? ... Queries or sets file attributes. (Tcl 8.0) file channels ?pattern? Returns the open channels in this interpreter, optionally filtered by the glob-stylepattern. (Tcl 8.3) file copy ?-force? source Copies file source to file destination. The source and destination can be directories. (Tcl 7.6) destination file delete ?-force? name Deletes the named file. (Tcl 7.6) file dirname name Returns parent directory of file name. file executable name Returns 1 if name has execute permission, else 0. file exists name Returns 1 if name exists, else 0. file extension name Returns the part of name from the last dot (i.e.,.) to the end. The dot is included in the return value. file isdirectory name Returns 1 if name is a directory, else 0. file isfile name Returns 1 if name is not a directory, symbolic link, or device, else 0. file join path path... Joins pathname components into a new pathname. (Tcl 7.5) file link ?-type? name ?target? Returns the link pointed to by name, or creates a link totarget if it is specified. type can be -hard or -symbolic. (Tcl 8.4) file lstat name var Places attributes of the link name into var. file mkdir name Creates directory name. (Tcl 7.6) file mtime name ?time? Returns modify time of name as a decimal string. Iftime is specified, the modify time of the file is set. file nativename name Returns the platform-native version of name. (Tk 8.0). file normalize name Returns a unique, absolute, path forname while eliminating extra /, /., and /.. components. (Tcl 8.4) file owned name Returns 1 if current user owns the filename, else 0. file pathtype name relative, absolute, or volumerelative. (Tcl 7.5) file readable name Returns 1 if name has read permission, else 0. file readlink name Returns the contents of the symbolic link name. file rename ?-force? old new Changes the name of old to new. (Tcl 7.6) file rootname name Returns all but the extension of name (i.e., up to but not including the last. in name). file separator ?name? Returns the default file separator character on this file system, or the separator character for name if it is specified. (Tcl 8.4) file size name Returns the number of bytes in name. file split name Splits name into its pathname components. (Tcl 7.5) This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks file stat name var Places attributes of name into array var. The elements defined forvar are listed in Table 9-3. file system name Returns a tuple of the filesystem for name (e.g. native or vfs) and the platform-specific type forname (e.g NTFS or FAT32). (Tcl 8.4) file tail name Returns the last pathname component ofname. file type name Returns type identifier, which is one of: file, directory, characterSpecial, blockSpecial, fifo, link, or socket. file volumes name Returns the available file volumes on this computer. On Unix, this always returns /. On Windows, this would be a list like {a:/ c:/}. (Tcl 8.3) file writable name [ Team LiB ] Returns 1 if name has write permission, else 0. . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Cross-Platform File Naming Files are named differently on UNIX, Windows, and Macintosh. UNIX separates file name components with a forward slash (/), Macintosh separates components with a colon (:), and Windows separates components with a backslash (\). In addition, the way that absolute and relative names are distinguished is different. For example, these are absolute pathnames for the Tcl script library (i.e., $tcl_library) on Macintosh, Windows, and UNIX, respectively: Disk:System Folder:Extensions:Tool Command Language:tcl7.6 c:\Program Files\Tcl\lib\Tcl7.6 /usr/local/tcl/lib/tcl7.6 The good news is that Tcl provides operations that let you deal with file pathnames in a platform-independent manner. The file operations described in this chapter allow either native format or the UNIX naming convention. The backslash used in Windows pathnames is especially awkward because the backslash is special to Tcl. Happily, you can use forward slashes instead: c:/Program Files/Tcl/lib/Tcl7.6 There are some ambiguous cases that can be specified only with native pathnames. On my Macintosh, Tcl and Tk are installed in a directory that has a slash in it. You can name it only with the native Macintosh name: Disk:Applications:Tcl/Tk 4.2 Another construct to watch out for is a leading // in a file name. This is the Windows syntax for network names that reference files on other computers. You can avoid accidentally constructing a network name by using the file join command described next. Of course, you can use network names to access remote files. If you must communicate with external programs, you may need to construct a file name in the native syntax for the current platform. You can construct these names with file join described later. You can also convert a UNIX-like name to a native name withfile nativename. Several of the file operations operate on pathnames as opposed to returning information about the file itself. You can use thedirname, extension, join, normalize, pathtype, rootname, split, and tail operations on any string; there is no requirement that the pathnames refer to an existing file. Building up Pathnames: file join You can get into trouble if you try to construct file names by simply joining components with a slash. If part of the name is in native format, joining things with slashes will result in incorrect pathnames on Macintosh and Windows. The same problem arises when you accept user input. The user is likely to provide file names in native format. For example, this construct will not create a valid pathname on the Macintosh because $tcl_library is in native format: set file $tcl_library/init.tcl Use file join to construct file names. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The platform-independent way to construct file names is with file join. The following command returns the name of theinit.tcl file in native format: set file [file join $tcl_library init.tcl] The file join operation can join any number of pathname components. In addition, it has the feature that an absolute pathname overrides any previous components. For example (on UNIX), /b/c is an absolute pathname, so it overrides any paths that come before it in the arguments to file join: file join a b/c d => a/b/c/d file join a /b/c d => /b/c/d On Macintosh, a relative pathname starts with a colon, and an absolute pathname does not. To specify an absolute path, you put a trailing colon on the first component so that it is interpreted as a volume specifier. These relative components are joined into a relative pathname: file join a :b:c d => :a:b:c:d In the next case, b:c is an absolute pathname withb: as the volume specifier. The absolute name overrides the previous relative name: file join a b:c d => b:c:d The file join operation converts UNIX-style pathnames to native format. For example, on Macintosh you get this: file join /usr/local/lib => usr:local:lib Chopping Pathnames: split, dirname, tail The file split command divides a pathname into components. It is the inverse offile join. The split operation detects automatically if the input is in native or UNIX format. The results of file split may contain some syntax to help resolve ambiguous cases when the results are passed back to file join. For example, on Macintosh a UNIX-style pathname is split on slash separators. The Macintosh syntax for a volume specifierDisk:) ( is returned on the leading component: file split "/Disk/System Folder/Extensions" => Disk: {System Folder} Extensions A common reason to split up pathnames is to divide a pathname into the directory part and the file part. This task is handled directly by the dirname and tail operations. The dirname operation returns the parent directory of a pathname, whiletail returns the trailing component of the pathname: file dirname /a/b/c => /a/b file tail /a/b/c => c This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks For a pathname with a single component, the dirname option returns ".", on UNIX and Windows, or ":" on Macintosh. This is the name of the current directory. The extension and root options are also complementary. The extension option returns everything from the last period in the name to the end (i.e., the file suffix including the period.) The root option returns everything up to, but not including, the last period in the pathname: file root /a/b.c => /a/b file extension /a/b.c => .c [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Manipulating Files and Directories Tcl 7.6 added file operations to copy files, delete files, rename files, and create directories. In earlier versions it was necessary to exec other programs to do these things, except on Macintosh, where cp, rm, mv, mkdir, and rmdir were built in. These commands are no longer supported on the Macintosh. Your scripts should use the file command operations described below to manipulate files in a platform-independent way. File name patterns are not directly supported by the file operations. Instead, you can use theglob command described on page 122 to get a list of file names that match a pattern. Copying Files The file copy operation copies files and directories. The following example copiesfile1 to file2. If file2 already exists, the operation raises an error unless the -force option is specified: file copy ?-force? file1 file2 Several files can be copied into a destination directory. The names of the source files are preserved. The -force option indicates that files under directory can be replaced: file copy ?-force? file1 file2 ... directory Directories can be recursively copied. The -force option indicates that files underdir2 can be replaced: file copy ?-force? dir1 dir2 Creating Directories The file mkdir operation creates one or more directories: file mkdir dir dir ... It is not an error if the directory already exists. Furthermore, intermediate directories are created if needed. This means that you can always make sure a directory exists with a single mkdir operation. Suppose /tmp has no subdirectories at all. The following command creates /tmp/sub1 and /tmp/sub1/sub2: file mkdir /tmp/sub1/sub2 The -force option is not understood by file mkdir, so the following command accidentally creates a folder named-force, as well as one named This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks oops. file mkdir -force oops Symbolic and Hard Links The file link operation allows the user to manipulate links. Hard links are directory entries that directly reference an existing file or directory. Symbolic (i.e., soft) links are files that contain the name of another file or directory. Generally, opening a link opens the file referenced by the link. Operating system support for links varies. Unix supports both types of links. Classic Macintosh only supports symbolic links (i.e., aliases). Windows 95/98/ME do not support links at all, while Windows NT/2000/XP support symbolic links to directories and hard links to files. With only a single argument, file link returns the value of a symbolic link, or raises an error if the file is not a symbolic link. With two pathname arguments, the first is the name of the link, and the second is the name of the file referenced by the link. If you leave out the -hard or -symbolic, the appropriate link type is created for the current platform: file link the_link the_existing_file Deleting Files The file delete operation deletes files and directories. It isnot an error if the files do not exist. A non-empty directory is not deleted unless the -force option is specified, in which case it is recursively deleted: file delete ?-force? name name ... To delete a file or directory named -force, you must specify a nonexistent file before the-force to prevent it from being interpreted as a flag (-force -force won't work): file delete xyzzy -force Renaming Files and Directories The file rename operation changes a file's name fromold to new. The -force option causes new to be replaced if it already exists. file rename ?-force? old new Using file rename is the best way to update an existing file. First, generate the new version of the file in a temporary file. Then, use file rename to replace the old version with the new version. This ensures that any other programs that access the file will not see the new version until it is This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks complete. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] File Attributes There are several file operations that return specific file attributes: atime, executable, exists, isdirectory, isfile, mtime, owned, readable, readlink, size and type. Refer to Table 9-2 on page 108 for their function. The following command usesfile mtime to compare the modify times of two files. If you have ever resorted to piping the results of ls -l into awk in order to derive this information in other shell scripts, you will appreciate this example: Example 9-2 Comparing file modify times proc newer { file1 file2 } { if {![file exists $file2]} { return 1 } else { # Assume file1 exists expr {[file mtime $file1] > [file mtime $file2]} } } You can use the optional time argument to mtime and atime to set the file's time attributes, like the Unixtouch command. The stat and lstat operations return a collection of file attributes. They take a third argument that is the name of an array variable, and they initialize that array with elements that contain the file attributes. If the file is a symbolic link, then the lstat operation returns information about the link itself and the stat operation returns information about the target of the link. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 9-3. Array elements defined by file stat atime The last access time, in seconds. ctime The last change time (not the create time), in seconds. dev The device identifier, an integer. gid The group owner, an integer. ino The file number (i.e., inode number), an integer. mode The permission bits. mtime The last modify time, in seconds. nlink The number of links, or directory references, to the file. size The number of bytes in the file. type file, directory, characterSpecial, blockSpecial, fifo, link, or socket. uid The owner's user ID, an integer. The array elements are listed in Table 9-3. All the element values are decimal strings, except fortype, which can have the values returned by the type option. The element names are based on the UNIXstat system call. Use thefile attributes command described later to get other platform-specific attributes. Example 9-3 uses the device (dev) and inode (ino) attributes of a file to determine whether two pathnames reference the same file. These attributes are UNIX specific; they are not well defined on Windows and Macintosh. Example 9-3 Determining whether pathnames reference the same file proc fileeq { path1 path2 } { file stat $path1 stat1 file stat $path2 stat2 expr {$stat1(ino) == $stat2(ino) && \ $stat1(dev) == $stat2(dev)} } The file attributes operation was added in Tcl 8.0 to provide access to platform-specific attributes. Theattributes operation lets you set and query attributes. The interface uses option-value pairs. With no options, all the current values are returned. file attributes book.doc => -creator FRAM -hidden 0 -readonly 0 -type MAKR These Macintosh attributes are explained in Table 9-4. The four-character type codes used on Macintosh are illustrated on page 600. With a single option, only that value is returned: file attributes book.doc -readonly => 0 The attributes are modified by specifying one or more option–value pairs. Setting attributes can raise an error if you do not have the right permissions: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks file attributes book.doc -readonly 1 -hidden 0 Table 9-4. Platform-specific file attributes -permissions File permission bits. mode is an octal number or symbolic representation (e.g.a+x) with bits defined by thechmod mode system call, or a simplified ls-style string of the form rwxrwxrwx (must be 9 characters). (UNIX) -group ID The group owner of the file. (UNIX) -owner ID The owner of the file. (UNIX) -archive bool The archive bit, which is set by backup programs. (Windows) -system bool If set, then you cannot remove the file. (Windows) -longname The long (expanded) version of the pathname. Read-only. (Windows) -shortname The short (8.3) version of the pathname. Read-only. (Windows) -hidden bool If set, then the file does not appear in listings. (Windows, Macintosh) -readonly bool If set, then you cannot write the file. (Windows, Macintosh) -creator type type is 4-character code of creating application. (Macintosh) -type type type is 4-character type code. (Macintosh) [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Input/Output Command Summary The following sections describe how to open, read, and write files. The basic model is that you open a file, read or write it, then close the file. Network sockets also use the commands described here. Socket programming is discussed in Chapter 17, and more advanced event-driven I/O is described in Chapter 16. Table 9-5 lists the basic commands associated with file I/O: Table 9-5. Tcl commands used for file access open what ?access? ?permissions? Returns channel ID for a file or pipeline. puts ?-nonewline? ?channel? string Writes a string. gets channel ?varname? Reads a line. read channel ?numBytes? Reads numBytes bytes, or all data. read -nonewline channel Reads all bytes and discard the last\n. tell channel Returns the seek offset. seek channel offset ?origin? Sets the seek offset. origin is one of start, current, or end. eof channel Queries end-of-file status. flush channel Writes buffers of a channel. close channel Closes an I/O channel. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Opening Files for I/O The open command sets up an I/O channel to either a file or a pipeline of processes. The return value ofopen is an identifier for the I/O channel. Store the result of open in a variable and use the variable as you used thestdout, stdin, and stderr identifiers in the examples so far. The basic syntax is: open what ?access? ?permissions? The what argument is either a file name or a pipeline specification similar to that used by the exec command. The access argument can take two forms, either a short character sequence that is compatible with the fopen library routine, or a list of POSIX access flags.Table 9-6 summarizes the first form, while Table 9-7 summarizes the POSIX flags. Ifaccess is not specified, it defaults to read. Example 9-4 Opening a file for writing set fileId [open /tmp/foo w 0600] puts $fileId "Hello, foo!" close $fileId The permissions argument is a value used for the permission bits on a newly created file. UNIX uses three bits each for the owner, group, and everyone else. The bits specify read, write, and execute permission. These bits are usually specified with an octal number, which has a leading zero, so that there is one octal digit for each set of bits. The default permission bits are 0666, which grant read/write access to everybody. Example 9-4 specifies 0600 so that the file is readable and writable only by the owner.0775 would grant read, write, and execute permissions to the owner and group, and read and execute permissions to everyone else. You can set other special properties with additional high-order bits. Consult the UNIX manual page on chmod command for more details. Table 9-6. Summary of the open access arguments r Opens for reading. The file must exist. r+ Opens for reading and writing. The file must exist. w Opens for writing. Truncate if it exists. Create if it does not exist. w+ Opens for reading and writing. Truncate or create. a Opens for writing. Data is appended to the file. a+ Opens for reading and writing. Data is appended. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 9-7. Summary of POSIX flags for the access argument RDONLY Opens for reading. WRONLY Opens for writing. RDWR Opens for reading and writing. APPEND Opens for append. CREAT Creates the file if it does not exist. EXCL If CREAT is also specified, then the file cannot already exist. NOCTTY Prevents terminal devices from becoming the controlling terminal. NONBLOCK Does not block during the open. TRUNC Truncates the file if it exists. The following example illustrates how to use a list of POSIX access flags to open a file for reading and writing, creating it if needed, and not truncating it. This is something you cannot do with the simpler form of the access argument: set fileId [open /tmp/bar {RDWR CREAT}] Catch errors from open. In general, you should check for errors when opening files. The following example illustrates a catch phrase used to open files. Recall that catch returns 1 if it catches an error; otherwise, it returns zero. It treats its second argument as the name of a variable. In the error case, it puts the error message into the variable. In the normal case, it puts the result of the command into the variable: Example 9-5 A more careful use of open if [catch {open /tmp/data r} fileId] { puts stderr "Cannot open /tmp/data: $fileId" } else { # Read and process the file, then... close $fileId } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Opening a Process Pipeline You can open a process pipeline by specifying the pipe character, |, as the first character of the first argument. The remainder of the pipeline specification is interpreted just as with the exec command, including input and output redirection. The second argument determines which end of the pipeline open returns. The following example runs the UNIXsort program on the password file, and it uses thesplit command to separate the output lines into list elements: Example 9-6 Opening a process pipeline set input [open "|sort /etc/passwd" r] set contents [split [read $input] \n] close $input You can open a pipeline for both read and write by specifying the r+ access mode. In this case, you need to worry about buffering. After aputs, the data may still be in a buffer in the Tcl library. Use the flush command to force the data out to the spawned processes before you try to read any output from the pipeline. You can also use the fconfigure command described on page 233 to force line buffering. Remember that read-write pipes will not work at all with Windows 3.1 because pipes are simulated with files. Event-driven I/O is also very useful with pipes. It means you can do other processing while the pipeline executes, and simply respond when the pipe generates data. This is described in Chapter 16. Expect If you are trying to do sophisticated things with an external application, you will find that the Expect extension provides a much more powerful interface than a process pipeline. Expect adds Tcl commands that are used to control interactive applications. It is extremely useful for automating a variety of applications such as ssh, Telnet, and programs under test. Tcl is able to handle simple FTP sessions, telnet and many command line controllable applications, but Expect has extra control at the tty level that is essential for certain applications. It comes on some systems as a specially built Tcl shell named expect, and it is also available as an extension that you can dynamically load into Tcl shells with: package require Expect Expect was created by Don Libes at the National Institute of Standards and Technology (NIST). Expect is described in Exploring Expect (Libes, O'Reilly & Associates, Inc., 1995). You can find the software on the CD and on the web at: http://expect.nist.gov/ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Reading and Writing The standard I/O channels are already open for you. There is a standard input channel, a standard output channel, and a standard error output channel. These channels are identified by stdin, stdout, and stderr, respectively. Other I/O channels are returned by theopen command, and by the socket command described on page 239. There may be cases when the standard I/O channels are not available. The wish shells on Windows and Macintosh have no standard I/O channels. Some UNIX window managers close the standard I/O channels when you start programs from window manager menus. You can also close the standard I/O channels with close. The puts and gets Commands The puts command writes a string and a newline to the output channel. There are a couple of details about the puts command that we have not yet used. It takes a -nonewline argument that prevents the newline character that is normally appended to the output channel. This is used in the prompt example below. The second feature is that the channel identifier is optional, defaulting to stdout if not specified. Note that you must use flush to force output of a partial line. This is illustrated inExample 9-7. Example 9-7 Prompting for input puts -nonewline "Enter value: " flush stdout ;# Necessary to get partial line output set answer [gets stdin] The gets command reads a line of input, and it has two forms. In the previous example, with just a single argument, gets returns the line read from the specified I/O channel. It discards the trailing newline from the return value. If end of file is reached, an empty string is returned. You must use the eof command to tell the difference between a blank line and end-of-file.eof returns 1 if there is end of file. Given a second varName argument, gets stores the line into a named variable and returns the number of bytes read. It discards the trailing newline, which is not counted. A -1 is returned if the channel has reached the end of file. Example 9-8 A read loop using gets while {[gets $channel line] >= 0} { # Process line } close $channel This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The read Command The read command reads blocks of data, and this capability is often more efficient. There are two forms forread: You can specify the -nonewline argument or the numBytes argument, but not both. Without numBytes, the whole file (or what is left in the I/O channel) is read and returned. The -nonewline argument causes the trailing newline to be discarded. Given a byte count argument,read returns that amount, or less if there is not enough data in the channel. The trailing newline is not discarded in this case. Example 9-9 A read loop using read and split foreach line [split [read $channel] \n] { # Process line } close $channel For moderate-sized files, it is about 10 percent faster to loop over the lines in a file using the read loop in the second example. In this case, read returns the whole file, andsplit chops the file into list elements, one for each line. For small files (less than 1K) it doesn't really matter. For large files (megabytes) you might induce paging with this approach. Platform-Specific End of Line Characters Tcl automatically detects different end of line conventions. On UNIX, text lines are ended with a newline character (\n). On Macintosh, they are terminated with a carriage return (\r). On Windows, they are terminated with a carriage return, newline sequence (\r\n). Tcl accepts any of these, and the line terminator can even change within a file. All these different conventions are converted to the UNIX style so that once read, text lines are always terminated with a newline character (\n). Both the read and gets commands do this conversion. During output, text lines are generated in the platform-native format. The automatic handling of line formats means that it is easy to convert a file to native format. You just need to read it in and write it out: puts -nonewline $out [read $in] To suppress conversions, use the fconfigure command, which is described in more detail on page 234. Example 9-10 demonstrates a File_Copy procedure that translates files to native format. It is complicated because it handles directories. Example 9-10 Copy a file and translate to native format proc File_Copy {src dest} { if {[file isdirectory $src]} { file mkdir $dest foreach f [glob -nocomplain [file join $src *]] { This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks File_Copy $f [file join $dest [file tail $f]] } return } if {[file isdirectory $dest]} { set dest [file join $dest [file tail $src]] } set in [open $src] set out [open $dest w] puts -nonewline $out [read $in] close $out ; close $in } Random Access I/O The seek and tell commands provide random access to I/O channels. Each channel has a current position called theseek offset. Each read or write operation updates the seek offset by the number of bytes transferred. The current value of the offset is returned by the tell command. The seek command sets the seek offset by an amount, which can be positive or negative, from an origin which is either start, current, or end. If you are dealing with files greater than 2GB in size, you will need Tcl 8.4 for its 64-bit file system support. Closing I/O Channels The close command is just as important as the others because it frees operating system resources associated with the I/O channel. If you forget to close a channel, it will be closed when your process exits. However, if you have a long-running program, like a Tk script, you might exhaust some operating system resources if you forget to close your I/O channels. The close command can raise an error. If the channel was a process pipeline and any of the processes wrote to their standard error channel, then Tcl believes this is an error. The error is raised when the channel to the pipeline is finally closed. Similarly, if any of the processes in the pipeline exit with a nonzero status, close raises an error. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The Current Directory — cd and pwd Every process has a current directory that is used as the starting point when resolving a relative pathname. The pwd command returns the current directory, and the cd command changes the current directory. Example 9-11 uses these commands. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Matching File Names with glob The glob command expands a pattern into the set of matching file names. The general form of theglob command is: glob ?options? pattern ?pattern? ... The pattern syntax is similar to the string match patterns: * matches zero or more characters. ? matches a single character. [abc] matches a set of characters. {a,b,c} matches any of a, b, or c. All other characters must match themselves. Table 9-8 lists the options for the glob command. Table 9-8. glob command options -directory dir Search for files in the directorydir. (Tcl 8.3) -join The remaining pattern arguments are treated as a single pattern obtained by joining them with directory separators. (Tcl 8.3) -nocomplain Causes glob to return an empty list if no files match. Otherwise an error is raised. -path path Search for files in the given path prefix path. Allows you to search in areas that may contain glob-sensitive characters. (Tcl 8.3) -tails Only return the part of each file found that follows the last directory named in the-directory or -path argument. (Tcl 8.4) -types types Only return files matching the types specified. -- Signifies the end of flags. Must be used ifpattern begins with a-. Unlike the glob matching in csh, the Tcl glob command matches only the names of existing files. Incsh, the {a,b} construct can match nonexistent names. In addition, the results of glob are not sorted. Use thelsort command to sort its result if you find it important. Example 9-11 shows the FindFile procedure, which traverses the file system hierarchy using recursion. At each iteration it saves its current directory and then attempts to change to the next subdirectory. A catch guards against bogus names. The glob command matches file names: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than Example 9-11 Finding a file by name proc FindFile { startDir namePat } { set pwd [pwd] if {[catch {cd $startDir} err]} { puts stderr $err return } foreach match [glob -nocomplain -- $namePat] { puts stdout [file join $startDir $match] } foreach file {[glob -nocomplain *]} { if [file isdirectory $file] { FindFile [file join $startDir $file] $namePat } } cd $pwd } The -types option allows for special filtered matching similar to the UNIXfind command. The first form is like the -type option of find: b (block special file), c (character special file), d (directory), f (plain file), l (symbolic link), p (named pipe), or s (socket), where multiple types may be specified in the list. Glob will return all files which match at least one of the types given. The second form specifies types where all the types given must match. These are r (readable), w (writable) and x (executable) as file permissions, and readonly and hidden as special cases. On the Macintosh, MacOS types and creators are also supported, where any item which is four characters long is assumed to be a MacOS type (e.g. TEXT). Items which are of the form{macintosh type XXXX} or {macintosh creator XXXX} will match types or creators respectively. Unrecognized types, or specifications of multiple MacOS types/creators will signal an error. The two forms may be mixed, so-types {d f r w} will find all regular files OR directories that have both read AND write permissions. Expanding Tilde in File Names The glob command also expands a leading tilde (~) in filenames. There are two cases: ~/ expands to the current user's home directory. ~user expands to the home directory of user. If you have a file that starts with a literal tilde, you can avoid the tilde expansion by adding a leading ./ (e.g., ./~foobar). [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The exit and pid Commands The exit command terminates your script. Note thatexit causes termination of the whole process that was running the script. If you supply an integer-valued argument to exit, then that becomes the exit status of the process. The pid command returns the process ID of the current process. This can be useful as the seed for a random number generator because it changes each time you run your script. It is also common to embed the process ID in the name of temporary files. You can also find out the process IDs associated with a process pipeline withpid: set pipe [open "|command"] set pids [pid $pipe] There is no built-in mechanism to control processes in the Tcl core. On UNIX systems you canexec the kill program to terminate a process: exec kill $pid [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Environment Variables Environment variables are a collection of string-valued variables associated with each process. The process's environment variables are available through the global array env. The name of the environment variable is the index, (e.g.,env(PATH)), and the array element contains the current value of the environment variable. If assignments are made to env, they result in changes to the corresponding environment variable. Environment variables are inherited by child processes, so programs run with the exec command inherit the environment of the Tcl script. The following example prints the values of environment variables. Example 9-12 Printing environment variable values proc printenv { args } { global env set maxl 0 if {[llength $args] == 0} { set args [lsort [array names env]] } foreach x $args { if {[string length $x] > $maxl} { set maxl [string length $x] } } incr maxl 2 foreach x $args { puts stdout [format "%*s = %s" $maxl $x $env($x)] } } printenv USER SHELL TERM => USER = welch SHELL = /bin/csh TERM = tx Note: Environment variables can be initialized for Macintosh applications by editing a resource of typeSTR# whose name is Tcl Environment Variables. This resource is part of the tclsh and wish applications. Follow the directions on page 28 for usingResEdit. The format of the resource values is NAME=VALUE. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The registry Command Windows uses the registry to store various system configuration information. The Windows tool to browse and edit the registry is called regedit. Tcl provides a registry command. It is a loadable package that you must load by using: package require registry The registry structure has keys, value names, and typed data. The value names are stored under a key, and each value name has data associated with it. The keys are organized into a hierarchical naming system, so another way to think of the value names is as an extra level in the hierarchy. The main point is that you need to specify both a key name and a value name in order to get something out of the registry. The key names have one of the following formats: \\hostname\rootname\keypath rootname\keypath rootname The rootname is one of HKEY_LOCAL_MACHINE, HKEY_PERFORMANCE_DATA, HKEY_USERS, HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_CURRENT_CONFIG, or HKEY_DYN_DATA. Tables 9-9 and 9-10 summarize the registry command and data types: Table 9-9. The registry command registry delete key ?valueName? Deletes the key and the named value, or it deletes all values under the key ifvalueName is not specified. registry get key valueName Returns the value associated with valueName under key. registry keys key ?pat? Returns the list of keys or value names underkey that match pat, which is a string match pattern. registry set key Creates key. registry set key valueName data ?type? Creates valueName under key with value data of the given type. Types are listed in Table 9-10. registry type key valueName Returns the type of valueName under key. registry values key ?pat? Returns the names of the values stored underkey that match pat, which is a string match pattern. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 9-10. The registry data types binary Arbitrary binary data. none Arbitrary binary data. expand_sz A string that contains references to environment variables with the%VARNAME% syntax. dword A 32-bit integer. dword_big_endian A 32-bit integer in the other byte order. It is represented in Tcl as a decimal string. link A symbolic link. multi_sz An array of strings, which are represented as a Tcl list. resource_list A device driver resource list. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Part II: Advanced Tcl Part II describes advanced programming techniques that support sophisticated applications. The Tcl interfaces remain simple, so you can quickly construct powerful applications. Chapter 10 describes eval, which lets you create Tcl programs on the fly. There are tricks with usingeval correctly, and a few rules of thumb to make your life easier. Chapter 11 describes regular expressions. This is the most powerful string processing facility in Tcl. This chapter includes a cookbook of useful regular expressions. Chapter 12 describes the library and package facility used to organize your code into reusable modules. Chapter 13 describes introspection and debugging. Introspection provides information about the state of the Tcl interpreter. Chapter 14 describes namespaces that partition the global scope for variables and procedures. Namespaces help you structure large Tcl applications. Chapter 15 describes the features that support Internationalization, including Unicode, other character set encodings, and message catalogs. Chapter 16 describes event-driven I/O programming. This lets you run process pipelines in the background. It is also very useful with network socket programming, which is the topic of Chapter 17. Chapter 18 describes TclHttpd, a Web server built entirely in Tcl. You can build applications on top of TclHttpd, or integrate the server into existing applications to give them a web interface. TclHttpd also supports regular Web sites. Chapter 19 describes Safe-Tcl and using multiple Tcl interpreters. If an interpreter is safe, then you can grant it restricted functionality. This is ideal for supporting network applets that are downloaded from untrusted sites, which is described in Chapter 20. Chapter 21 describes how to use the Thread extension to create multi-threaded Tcl scripts. The extension provides threads, synchronization with mutexes and condition variables, shared variables, and thread pools. Chapter 22 describes how to package and deploy Tcl applications as Starkits. A Virtual File System facility is used to create a private file system inside the Starkit to hold the scripts, graphics, and documentation that make up your application. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 10. Quoting Issues and Eval This chapter describes explicit calls to the interpreter with the eval command. An extra round of substitutions is performed that results in some useful effects. The chapter describes the quoting problems with eval and the ways to avoid them. Theuplevel command evaluates commands in a different scope. The subst command does substitutions but no command invocation. Dynamic evaluation makes Tcl flexible and powerful, but it can be tricky to use properly. The basic idea is that you create a string and then use the eval command to interpret that string as a command or a series of commands. Creating program code on the fly is easy with an interpreted language like Tcl, and very hard, if not impossible, with a statically compiled language like C++ or Java. There are several ways that dynamic code evaluation is used in Tcl: In some cases, a simple procedure isn't quite good enough, and you need to glue together a command from a few different pieces and then execute the result using eval. This often occurs withwrappers, which provide a thin layer of functionality over existing commands. Callbacks are script fragments that are saved and evaluated later in response to some event. Examples include the commands associated with Tk buttons, fileevent I/O handlers, and after timer handlers. Callbacks are a flexible way to link different parts of an application together. You can add new control structures to Tcl using the uplevel command. For example, you can write a function that applies a command to each line in a file or each node in a tree. You can have a mixture of code and data, and just process the code part with the subst command. For example, this is useful in HTML templates described in Chapter 18. There are also some powerful combinations ofsubst and regsub described in Chapter 11. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Constructing Code with the list Command It can be tricky to assemble a command so that it is evaluated properly by eval. The same difficulties apply to commands likeafter, uplevel, and the Tk send command, all of which have similar properties toeval, except that the command evaluation occurs later or in a different context. Constructing commands dynamically is a source of many problems. The worst part is that you can write code that works sometimes but not others, which can be very confusing. Use list when constructing commands. The root of the quoting problems is the internal use of concat by eval and similar commands to concatenate their arguments into one command string. The concat can lose some important list structure so that arguments are not passed through as you expect. The general strategy to avoid these problems is to use list and lappend to explicitly form the command callback as a single, well-structured list. The eval Command The eval command results in another call to the Tcl interpreter. If you construct a command dynamically, you must useeval to interpret it. For example, suppose we want to construct the following command now but execute it later: puts stdout "Hello, World!" In this case, it is sufficient to do the following: set cmd {puts stdout "Hello, World!"} => puts stdout "Hello, World!" # sometime later... eval $cmd => Hello, World! In this case, the value of cmd is passed to Tcl. All the standard grouping and substitution are done again on the value, which is a puts command. However, suppose that part of the command is stored in a variable, but that variable will not be defined at the time eval is used. We can artificially create this situation like this: set string "Hello, World!" This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set cmd {puts stdout $string} => puts stdout $string unset string eval $cmd => can't read "string": no such variable In this case, the command contains $string. When this is processed byeval, the interpreter looks for the current value ofstring, which is undefined. This example is contrived, but the same problem occurs if string is a local variable, andcmd will be evaluated later in the global scope. A common mistake is to use double quotes to group the command. That will let $string be substituted now. However, this works only if string has a simple value, but it fails if the value of string contains spaces or other Tcl special characters: set cmd "puts stdout $string" => puts stdout Hello, World! eval $cmd => bad argument "World!": should be "nonewline" The problem is that we have lost some important structure. The identity of $string as a single argument gets lost in the second round of parsing by eval. The solution to this problem is to construct the command usinglist, as shown in the following example: Example 10-1 Using list to construct commands set string "Hello, World!" set cmd [list puts stdout $string] => puts stdout {Hello, World!} unset string eval $cmd => Hello, World! The trick is that list has formed a list containing three elements:puts, stdout, and the value of string. The substitution of $string occurs before list is called, and list takes care of grouping that value for us. In contrast, using double quotes is equivalent to: set cmd [concat puts stdout $string] Double quotes lose list structure. The problem here is that concat does not preserve list structure. The main lesson is that you should uselist to construct commands if they contain variable values or command results that must be substituted now. If you use double quotes, the values are substituted but you lose proper command structure. If you use curly braces, then values are not substituted until later, which may not be in the right context. Commands That Concatenate Their Arguments This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The uplevel, after and send commands concatenate their arguments into a command and execute it later in a different context. The uplevel command is described on page 138, after is described on page 228, and send is described on page 648. Whenever I discover such a command, I put it on my danger list and make sure I explicitly form a single command argument with list instead of letting the command concat items for me. Get in the habit now: after 100 [list doCmd $param1 $param2] send $interp [list doCmd $param1 $param2] ;# Safe! The danger here is that concat and list can result in the same thing, so you can be led down the rosy garden path only to get errors later when values change. The two previous examples always work. The next two work only if param1 and param2 have values that are single list elements: after 100 doCmd $param1 $param2 send $interp doCmd $param1 $param2 ;# Unsafe! If you use other Tcl extensions that provide eval-like functionality, carefully check their documentation to see whether they contain commands that concat their arguments into a command. For example, Tcl-DP, which provides a network version ofsend, dp_send, also uses concat. Commands That Use Callbacks The general strategy of passing out a command or script to call later is a flexible way to assemble different parts of an application, and it is widely used by Tcl commands. Examples include commands that are called when users click on Tk buttons, commands that are called when I/O channels have data ready, or commands that are called when clients connect to network servers. It is also easy to write your own procedures or C extensions that accept scripts and call them later in response to some event. These other callback situations may not appear to have the "concat problem" because they take a single script argument. However, as soon as you use double quotes to group that argument, you have created the concat problem all over again. So, all the caveats about usinglist to construct these commands still apply. Command Prefix Callbacks There is a variation on command callbacks called a command prefix. In this case, the command is given additional arguments when it is invoked. In other words, you provide only part of the command, the command prefix, and the module that invokes the callback adds additional arguments before using eval to invoke the command. For example, when you create a network server, you supply a procedure that is called when a client makes a connection. That procedure is called with three additional arguments that indicate the client's socket, IP address, and port number. This is described in more detail on page 240. The tricky thing is that you can define your callback procedure to take four (or more) arguments. In this case you specify some of the parameters when you define the callback, and then the socket subsystem specifies the remaining arguments when it makes the callback. The following command creates the server side of a socket: set virtualhost www.beedub.com socket -server [list Accept $virtualhost] 8080 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks However, you define the Accept procedure like this: proc Accept {myname sock ipaddr port} { ... } The myname parameter is set when you construct the command prefix. The remaining parameters are set when the callback is invoked. The use of list in this example is not strictly necessary because "we know" thatvirtualhost will always be a single list element. However, usinglist is just a good habit when forming callbacks, so I always write the code this way. There are many other examples of callback arguments that are really command prefixes. Some of these include the scrolling callbacks between Tk scrollbars and their widgets, the command aliases used with Safe Tcl, the sorting functions in lsort, and the completion callback used with fcopy. Example 13-6 on page 191 shows how to use eval to make callbacks from Tcl procedures. Constructing Procedures Dynamically The previous examples have all focused on creating single commands by using list operations. Suppose you want to create a whole procedure dynamically. Unfortunately, this can be particularly awkward because a procedure body is not a simple list. Instead, it is a sequence of commands that are each lists, but they are separated by newlines or semicolons. In turn, some of those commands may be loops and if commands that have their own command bodies. To further compound the problem, you typically have two kinds of variables in the procedure body: some that are to be used as values when constructing the body, and some that are to be used later when executing the procedure. The result can be very messy. The main trick to this problem is to use either format or regsub to process a template for your dynamically generated procedure. If you use format, then you can put %s into your templates where you want to insert values. You may find the positional notation of the format string (e.g., %1$s and %2$s) useful if you need to repeat a value in several places within your procedure body. The following example is a procedure that generates a new version of other procedures. The new version includes code that counts the number of times the procedure was called and measures the time it takes to run: Example 10-2 Generating procedures dynamically with a template proc TraceGen {procName} { rename s$procName $procName-orig set arglist {} foreach arg [info args $procName-orig] { append arglist "\$$arg " } proc $procName [info args $procName-orig] [format { global _trace_count _trace_msec incr _trace_count(%1$s) incr _trace_msec(%1$s) [lindex [time { set result [%1$s-orig %2$s] } 1] 0] return $result } $procName $arglist] } Suppose that we have a trivial procedure foo: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks proc foo {x y} { return [expr $x * $y] } If you run TraceGen on it and look at the results, you see this: TraceGen foo info body foo => global _trace_count _trace_msec incr _trace_count(foo) incr _trace_msec(foo) [lindex [time { set result [foo-orig $x $y] } 1] 0] return $result The tracing provided by TraceGen is similar to what you can achieve with the features of the Tcl 8.4trace command. With command tracing, which is described on page 194, you can track the calls and results of procedures. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Exploiting the concat inside eval The previous section warns about the danger of concatenation when forming commands. However, there are times when concatenation is done for good reason. This section illustrates cases where the concat done by eval is useful in assembling a command by concatenating multiple lists into one list. A concat is done internally by eval when it gets more than one argument: eval list1 list2 list3 ... The effect of concat is to join all the lists into one list; a new level of list structure isnot added. This is useful if the lists are fragments of a command. It is common to use this form of eval with the args construct in procedures. Use theargs parameter to pass optional arguments through to another command. Invoke the other command with eval, and the values in$args get concatenated onto the command properly. The special args parameter is illustrated in Example 7-2 on page 88. Using eval in a Wrapper Procedure. Here, we illustrate the use of eval and $args with a simple Tk example. In Tk, thebutton command creates a button in the user interface. The button command can take many arguments, and commonly you simply specify the text of the button and the Tcl command that is executed when the user clicks on the button: button .foo -text Foo -command foo After a button is created, it is made visible by packing it into the display. The pack command can also take many arguments to control screen placement. Here, we just specify a side and let the packer take care of the rest of the details: pack .foo -side left Even though there are only two Tcl commands to create a user interface button, we will write a procedure that replaces the two commands with one. Our first version might be: proc PackedButton {name txt cmd} { button $name -text $txt -command $cmd pack $name -side left } This is not a very flexible procedure. The main problem is that it hides the full power of the Tk button command, which can really take more than 30 widget configuration options, such as -background, -cursor, -relief, and more. They are listed on page 459. For example, you can easily make a red button like this: button .foo -text Foo -command foo -background red A better version of PackedButton uses args to pass through extra configuration options to the button command. The args parameter is a list of This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks all the extra arguments passed to the Tcl procedure. My first attempt to use $args looked like this, but it was not correct: proc PackedButton {name txt cmd args} { button $name -text $txt -command $cmd $args pack $name -side left } PackedButton .foo "Hello, World!" {exit} -background red => unknown option "-background red" The problem is that $args is a list value, andbutton gets the whole list as a single argument. Instead,button needs to get the elements of $args as individual arguments. Use eval with $args In this case, you can use eval because it concatenates its arguments to form a single list before evaluation. The single list is, by definition, the same as a single Tcl command, so the button command parses correctly. Here we giveeval two lists, which it joins into one command: eval {button $name -text $txt -command $cmd} $args The use of the braces in this command is discussed in more detail below. We also generalize our procedure to take some options to the pack command. This argument, pack, must be a list of packing options. The final version ofPackedButton is shown in Example 10-3: Example 10-3 Using eval with $args # PackedButton creates and packs a button. proc PackedButton {path txt cmd {pack {-side right}} args} { eval {button $path -text $txt -command $cmd} $args eval {pack $path} $pack } In PackedButton, both pack and args are list-valued parameters that are used as parts of a command. The internalconcat done by eval is perfect for this situation. The simplest call to PackedButton is: PackedButton .new "New" { New } The quotes and curly braces are redundant in this case but are retained to convey some type information. The quotes imply a string label, and the braces imply a command. The pack argument takes on its default value, and theargs variable is an empty list. The two commands executed by PackedButton are: button .new -text New -command New pack .new -side right PackedButton creates a horizontal stack of buttons by default. The packing can be controlled with a packing specification: PackedButton .save "Save" { Save $file } {-side left} The two commands executed by PackedButton are: button .new -text Save -command { Save $file } . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks pack .new -side left The remaining arguments, if any, are passed through to the button command. This lets the caller fine-tune some of the button attributes: PackedButton .quit Quit { Exit } {-side left -padx 5} \ -background red The two commands executed by PackedButton are: button .quit -text Quit -command { Exit } -background red pack .quit -side left -padx 5 You can see a difference between the pack and args argument in the call to PackedButton. You need to group the packing options explicitly into a single argument. The args parameter is automatically made into a list of all remaining arguments. In fact, if you group the extra button parameters, it will be a mistake: PackedButton .quit Quit { Exit } {-side left -padx 5} \ {-background red} => unknown option "-background red" Correct Quoting with eval What about the peculiar placement of braces in PackedButton? eval {button $path -text $txt -command $cmd} $args By using braces, we control the number of times different parts of the command are seen by the Tcl evaluator. Without any braces, everything goes through two rounds of substitution. The braces prevent one of those rounds. In the above command, only $args is substituted twice. Before eval is called, the $args is replaced with its list value. Then,eval is invoked, and it concatenates its two list arguments into one list, which is now a properly formed command. The second round of substitutions done by eval replaces the txt and cmd values. Do not use double quotes with eval. You may be tempted to use double quotes instead of curly braces in your uses of eval. Don't give in! Using double quotes is, mostly likely, wrong. Suppose the first eval command is written like this: eval "button $path -text $txt -command $cmd $args" Incidentally, the previous is equivalent to: eval button $path -text $txt -command $cmd $args These versions happen to work with the following call because txt and cmd have one-word values with no special characters in them: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks PackedButton .quit Quit { Exit } The button command that is ultimately evaluated is: button .quit -text Quit -command { Exit } In the next call, an error is raised: PackedButton .save "Save As" [list Save $file] => unknown option "As" This is because the button command is this: button .save -text Save As -command Save /a/b/c But it should look like this instead: button .save -text {Save As} -command {Save /a/b/c} The problem is that the structure of the button command is now wrong. The value of txt and cmd are substituted first, before eval is even called, and then the whole command is parsed again. The worst part is that sometimes using double quotes works, and sometimes it fails. The success of using double quotes depends on the value of the parameters. When those values contain spaces or special characters, the command gets parsed incorrectly. Braces: the one true way to group arguments toeval. To repeat, the safe construct is: eval {button $path -text $txt -command $cmd} $args The following variations are also correct. The first uses list to do quoting automatically, and the others use backslashes or braces to prevent the extra round of substitutions: eval [list button $path -text $txt -command $cmd] $args eval button \$path -text \$txt -command \$cmd $args eval button {$path} -text {$txt} -command {$cmd} $args Finally, here is one more incorrect approach that tries to quote by hand: eval "button {$path} -text {$txt} -command {$cmd} $args" The problem is that double quotes disable the quoting you normally expect with curly braces. Consider this little example that uses double quotes. The curly braces around $blob have no special effect, and the interpreter sees unbalanced braces: set blob "foo\{bar space" => foo{bar space eval "puts {$blob}" => missing close brace If we group instead with curly braces, then the variable substitution occurs once, after the arguments to puts have been grouped, and there is no error. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks eval puts {$blob} => foo{bar space You can also be successful using list: eval puts [list $blob] Of course, these simple examples are contrived, but they illustrate the need to be careful with your list construction when using eval! [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] The uplevel Command The uplevel command is similar to eval, except that it evaluates a command in a different scope than the current procedure. It is useful for defining new control structures entirely in Tcl. The syntax for uplevel is: uplevel ?level? command ?list1 list2 ...? As with upvar, the level parameter is optional, but recommended for good style, and defaults to1, which means to execute the command in the scope of the calling procedure. The other common use of level is #0, which means to evaluate the command in the global scope. You can count up farther than one (e.g., 2 or 3), or count down from the global level (e.g.,#1 or #2), but these cases rarely make sense. When you specify the command argument, you must be aware of any substitutions that might be performed by the Tcl interpreter before uplevel is called. If you are entering the command directly, protect it with curly braces so that substitutions occur in the other scope. The following affects the variable x in the caller's scope: uplevel {set x [expr $x + 1]} However, the following will use the value of x in the current scope to define the value ofx in the calling scope, which is probably not what was intended: uplevel "set x [expr $x + 1]" If you are constructing the command dynamically, again use list. This fragment is used later inExample 10-4: uplevel [list foreach $args $valueList {break}] It is common to have the command in a variable. This is the case when the command has been passed into your new control flow procedure as an argument In this case, you should evaluate the command one level up. Put the level in explicitly to avoid cases where $cmd looks like a number! uplevel 1 $cmd Another common scenario is reading commands from users as part of an application. In this case, you should evaluate the command at the global scope. Example 16-2 on page 230 illustrates this use of uplevel: uplevel #0 $cmd If you are assembling a command from a few different lists, such as the args parameter, then you can use concat to form the command: uplevel [concat $cmd $args] The lists in $cmd and $args are concatenated into a single list, which is a valid Tcl command. Likeeval, uplevel uses concat internally if it is given extra arguments, so you can leave out the explicit use of concat. The following commands are equivalent: uplevel [concat $cmd $args] uplevel "$cmd $args" uplevel $cmd $args Example 10-4 shows list assignment using the foreach trick described on Page 81. List assignment is useful if a command returns several values in a list. The lassign procedure assigns the list elements to several variables. Thelassign procedure hides the foreach trick, but it must use the uplevel command so that the loop variables get assigned in the correct scope. Thelist command is used to construct theforeach This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than command that is executed in the caller's scope. This is necessary so that $variables and $values get substituted before the command is evaluated in the other scope. Example 10-4 lassign: list assignment with foreach # Assign a set of variables from a list of values. # If there are more values than variables, they are returned. # If there are fewer values than variables, # the variables get the empty string. proc lassign {valueList args} { if {[llength $args] == 0} { error "wrong # args: lassign list varname ?varname..?" } if {[llength $valueList] == 0} { # Ensure one trip through the foreach loop set valueList [list {}] } uplevel 1 [list foreach $args $valueList {break}] return [lrange $valueList [llength $args] end] } Example 10-5 illustrates a new control structure with theFile_Process procedure that applies a callback to each line in a file. The call touplevel allows the callback to be concatenated with the line to form the command. The list command is used to quote any special characters inline, so it appears as a single argument to the command. Example 10-5 The File_Process procedure iterates over lines in a file proc File_Process {file callback} { set in [open $file] while {[gets $in line] >= 0} { uplevel 1 $callback [list $line] } close $in } What is the difference between these two commands? uplevel 1 [list $callback $line] uplevel 1 $callback [list $line] The first form limits callback to be the name of the command, while the second form allowscallback to be a command prefix. Once again, what is the bug with this version? uplevel 1 $callback $line The arbitrary value of $line is concatenated to the callback command, and it is likely to be a malformed command when executed. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The subst Command The subst command is useful when you have a mixture of Tcl commands, Tcl variable references, and plain old data. Thesubst command looks through the data for square brackets, dollar signs, and backslashes, and it does substitutions on those. It leaves the rest of the data alone: set a "foo bar" subst {a=$a date=[exec date]} => a=foo bar date=Thu Dec 15 10:13:48 PST 1994 The subst command does not honor the quoting effect of curly braces. It does substitutions regardless of braces: subst {a=$a date={[exec date]}} => a=foo bar date={Thu Dec 15 10:15:31 PST 1994} You can use backslashes to prevent variable and command substitution. subst {a=\$a date=\[exec date]} => a=$a date=[exec date] You can use other backslash substitutions like\uXXXX to get Unicode characters, \n to get newlines, or \-newline to hide newlines. The subst command takes flags that limit the substitutions it will perform. The flags are-nobackslashes, -nocommands, or -novariables. You can specify one or more of these flags before the string that needs to be substituted: subst -novariables {a=$a date=[exec date]} => a=$a date=Thu Dec 15 10:15:31 PST 1994 String Processing with subst The subst command can be used with theregsub command to do efficient, two-step string processing. In the first step,regsub is used to rewrite an input string into data with embedded Tcl commands. In the second step, subst or eval replaces the Tcl commands with their result. By artfully mapping the data into Tcl commands, you can dynamically construct a Tcl script that processes the data. The processing is efficient because the Tcl parser and the regular expression processor have been highly tuned. Chapter 11 has several examples that use this technique. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 11. Regular Expressions This chapter describes regular expression pattern matching and string processing based on regular expression substitutions. These features provide the most powerful string processing facilities in Tcl. Tcl commands described are: regexp and regsub. Regular expressions are a formal way to describe string patterns. They provide a powerful and compact way to specify patterns in your data. Even better, there is a very efficient implementation of the regular expression mechanism due to Henry Spencer. If your script does much string processing, it is worth the effort to learn about the regexp command. Your Tcl scripts will be compact and efficient. This chapter uses many examples to show you the features of regular expressions. Regular expression substitution is a mechanism that lets you rewrite a string based on regular expression matching. The regsub command is another powerful tool, and this chapter includes several examples that do a lot of work in just a few Tcl commands. Stephen Uhler has shown me several ways to transform input data into a Tcl script with regsub and then use subst or eval to process the data. The idea takes a moment to get used to, but it provides a very efficient way to process strings. Tcl 8.1 added a new regular expression implementation that supports Unicode and advanced regular expressions (ARE). This implementation adds more syntax and escapes that makes it easier to write patterns, once you learn the new features! If you know Perl, then you are already familiar with these features. The Tcl advanced regular expressions are almost identical to the Perl 5 regular expressions. The new features include a few very minor incompatibilities with the regular expressions implemented in earlier versions of Tcl 8.0, but these rarely occur in practice. The new regular expression package supports Unicode, of course, so you can write patterns to match Japanese or Hindi documents! [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks . When to Use Regular Expressions Regular expressions can seem overly complex at first. They introduce their own syntax and their own rules, and you may be tempted to use simpler commands like string first, string range, or string match to process your strings. However, often a single regular expression command can replace a sequence of several string commands. Not only do you have to write less code, but you often get a performance improvement because the regular expression matcher is implemented in optimized C code, so pattern matching is fast. The regular expression matcher does more than test for a match. It also tells you what part of your input string matches the pattern. This is useful for picking data out of a large input string. In fact, you can capture several pieces of data in just one match by using subexpressions. The regexp Tcl command makes this easy by assigning the matching data to Tcl variables. If you find yourself using string first and string range to pick out data, remember that regexp can do it in one step instead. The regular expression matcher is structured so that patterns are first compiled into an form that is efficient to match. If you use the same pattern frequently, then the expensive compilation phase is done only once, and all your matching uses the efficient form. These details are completely hidden by the Tcl interface. If you use a pattern twice, Tcl will nearly always be able to retrieve the compiled form of the pattern. As you can see, the regular expression matcher is optimized for lots of heavy-duty string processing. Avoiding a Common Problem Group your patterns with curly braces. One of the stumbling blocks with regular expressions is that they use some of the same special characters as Tcl. Any pattern that contains brackets, dollar signs, or spaces must be quoted when used in a Tcl command. In many cases you can group the regular expression with curly braces, so Tcl pays no attention to it. However, when using Tcl 8.0 (or earlier) you may need Tcl to do backslash substitutions on part of the pattern, and then you need to worry about quoting the special characters in the regular expression. Advanced regular expressions eliminate this problem because backslash substitution is now done by the regular expression engine. Previously, to get \n to mean the newline character (or \t for tab) you had to let Tcl do the substitution. With Tcl 8.1,\n and \t inside a regular expression mean newline and tab. In fact, there are now about 20 backslash escapes you can use in patterns. Now more than ever, remember to group your patterns with curly braces to avoid conflicts between Tcl and the regular expression engine. The patterns in the first sections of this chapter ignore this problem. The sample expressions in Table 11-7 on page 161 are quoted for use within Tcl scripts. Most are quoted simply by putting the whole pattern in braces, but some are shown without braces for comparison. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Regular Expression Syntax This section describes the basics of regular expression patterns, which are found in all versions of Tcl. There are occasional references to features added by advanced regular expressions, but they are covered in more detail starting on page 149. There is enough syntax in regular expressions that there are five tables that summarize all the options. These tables appear together starting at page 154. A regular expression is a sequence of the following items: A literal character. A matching character, character set, or character class. A repetition quantifier. An alternation clause. A subpattern grouped with parentheses. Matching Characters Most characters simply match themselves. The following pattern matches an a followed by ab: ab The general wild-card character is the period, ".". It matches any single character. The following pattern matches ana followed by any character: a. Remember that matches can occur anywhere within a string; a pattern does not have to match the whole string. You can change that by using anchors, which are described on page 147. Character Sets This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The matching character can be restricted to a set of characters with the [xyz] syntax. Any of the characters between the two brackets is allowed to match. For example, the following matches either Hello or hello: [Hh]ello The matching set can be specified as a range over the character set with the [x-y] syntax. The following matches any digit: [0-9] There is also the ability to specify the complement of a set. That is, the matching character can be anything except what is in the set. This is achieved with the [^xyz] syntax. Ranges and complements can be combined. The following matches anything except the uppercase and lowercase letters: [^a-zA-Z] Using special characters in character sets. If you want a ] in your character set, put it immediately after the initial opening bracket. You do not need to do anything special to include [ in your character set. The following matches any square brackets or curly braces: [][{}] Most regular expression syntax characters are no longer special inside character sets. This means you do not need to backslash anything inside a bracketed character set except for backslash itself. The following pattern matches several of the syntax characters used in regular expressions: [][+*?()|\\] Advanced regular expressions add names and backslash escapes as shorthand for common sets of characters like white space, alpha, alphanumeric, and more. These are described on page 149 and listed in Table 11-3 on page 156. Quantifiers Repetition is specified with *, for zero or more,+, for one or more, and?, for zero or one. Thesequantifiers apply to the previous item, which is either a matching character, a character set, or a subpattern grouped with parentheses. The following matches a string that contains b followed by zero or more a's: ba* You can group part of the pattern with parentheses and then apply a quantifier to that part of the pattern. The following matches a string that has one or more sequences of ab: (ab)+ This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The pattern that matches anything, even the empty string, is: .* These quantifiers have a greedy matching behavior: They match as many characters as possible. Advanced regular expressions add nongreedy matching, which is described on page 151. For example, a pattern to match a single line might look like this: .*\n However, as a greedy match, this will match all the lines in the input, ending with the last newline in the input string. The following pattern matches up through the first newline. [^\n]*\n We will shorten this pattern even further on page 151 by using nongreedy quantifiers. There are also special newline sensitive modes you can turn on with some options described on page 153. Alternation Alternation lets you test more than one pattern at the same time. The matching engine is designed to be able to test multiple patterns in parallel, so alternation is efficient. Alternation is specified with |, the pipe symbol. Another way to match eitherHello or hello is: hello|Hello You can also write this pattern as: (h|H)ello or as: [hH]ello Anchoring a Match By default a pattern does not have to match the whole string. There can be unmatched characters before and after the match. You can anchor the match to the beginning of the string by starting the pattern with ^, or to the end of the string by ending the pattern with$. You can force the pattern to match the whole string by using both. All strings that begin with spaces or tabs are matched with: ^[ \t]+ If you have many text lines in your input, you may be tempted to think of ^ as meaning "beginning of line" instead of "beginning of string." By default, the ^ and $ anchors are relative to the whole input, and embedded newlines are ignored. Advanced regular expressions support options that make the ^ and $ anchors line-oriented. They also add the\A and \Z anchors that always match the beginning and end of the string, respectively. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Backslash Quoting Use the backslash character to turn off these special characters : .*?+[]()^$|\ For example, to match the plus character, you will need: \+ Remember that this quoting is not necessary inside a bracketed expression (i.e., a character set definition.) For example, to match either plus or question mark, either of these patterns will work: (\+|\?) [+?] To match a single backslash, you need two. You must do this everywhere, even inside a bracketed expression. Or you can use \B, which was added as part of advanced regular expressions. Both of these match a single backslash: \\ \B Unknown backslash sequences are an error. Versions of Tcl before 8.1 ignored unknown backslash sequences in regular expressions. For example, \= was just =, and \w was just w. Even \n was just n, which was probably frustrating to many beginners trying to get a newline into their pattern. Advanced regular expressions add backslash sequences for tab, newline, character classes, and more. This is a convenient improvement, but in rare cases it may change the semantics of a pattern. Usually these cases are where an unneeded backslash suddenly takes on meaning, or causes an error because it is unknown. Matching Precedence If a pattern can match several parts of a string, the matcher takes the match that occurs earliest in the input string. Then, if there is more than one match from that same point because of alternation in the pattern, the matcher takes the longest possible match. The rule of thumb is: first, then longest. This rule gets changed by nongreedy quantifiers that prefer a shorter match. Watch out for *, which means zero or more, because zero of anything is pretty easy to match. Suppose your pattern is: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [a-z]* This pattern will match against 123abc, but not how you expect. Instead of matching on the letters in the string, the pattern will match on the zero-length substring at the very beginning of the input string! This behavior can be seen by using the -indices option of the regexp command described on page 158. This option tells you the location of the matching string instead of the value of the matching string. Capturing Subpatterns Use parentheses to capture a subpattern. The string that matches the pattern within parentheses is remembered in a matching variable, which is a Tcl variable that gets assigned the string that matches the pattern. Using parentheses to capture subpatterns is very useful. Suppose we want to get everything between the <td> and </td> tags in some HTML. You can use this pattern: <td>([^<]*)</td> The matching variable gets assigned the part of the input string that matches the pattern inside the parentheses. You can capture many subpatterns in one match, which makes it a very efficient way to pick apart your data. Matching variables are explained in more detail on page 158 in the context of the regexp command. Sometimes you need to introduce parentheses but you do not care about the match that occurs inside them. The pattern is slightly more efficient if the matcher does not need to remember the match. Advanced regular expressions add noncapturing parentheses with this syntax: (?:pattern) [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Advanced Regular Expressions The syntax added by advanced regular expressions is mostly just shorthand notation for constructs you can make with the basic syntax already described. There are also some new features that add additional power: nongreedy quantifiers, back references, look-ahead patterns, and named character classes. If you are just starting out with regular expressions, you can ignore most of this section, except for the one about backslash sequences. Once you master the basics, of if you are already familiar with regular expressions in Tcl (or the UNIX vi editor or grep utility), then you may be interested in the new features of advanced regular expressions. Compatibility with Patterns in Tcl 8.0 Advanced regular expressions add syntax in an upward compatible way. Old patterns continue to work with the new matcher, but advanced regular expressions will raise errors if given to old versions of Tcl. For example, the question mark is used in many of the new constructs, and it is artfully placed in locations that would not be legal in older versions of regular expressions. The added syntax is summarized in Table 11-2 on page 155. If you have unbraced patterns from older code, they are very likely to be correct in Tcl 8.1 and later versions. For example, the following pattern picks out everything up to the next newline. The pattern is unbraced, so Tcl substitutes the newline character for each occurrence of \n. The square brackets are quoted so that Tcl does not think they delimit a nested command: regexp "(\[^\n\]+)\n" $input The above command behaves identically when using advanced regular expressions, although you can now also write it like this: regexp {([^\n]+)\n} $input The curly braces hide the brackets from the Tcl parser, so they do not need to be escaped with backslash. This saves us two characters and looks a bit cleaner. Backslash Escape Sequences The most significant change in advanced regular expression syntax is backslash substitutions. In Tcl 8.0 and earlier, a backslash is only used to turn off special characters such as: . + * ? [ ]. Otherwise it was ignored. For example,\n was simply n to the Tcl 8.0 regular expression engine. This was a source of confusion, and it meant you could not always quote patterns in braces to hide their special characters from Tcl's parser. In advanced regular expressions, \n now means the newline character to the regular expression engine, so you should never need to let Tcl do backslash processing. Again, always group your pattern with curly braces to avoid confusion. Advanced regular expressions add a lot of new backslash sequences. They are listed in Table 11-4 on page 156. Some of the more useful ones include \s, which matches space-like characters, \w, which matches letters, digit, and the underscore,\y, which matches the beginning or This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than end of a word, and \B, which matches a backslash. Character Classes Character classes are names for sets of characters. The named character class syntax is valid only inside a bracketed character set. The syntax is: [:identifier:] For example, alpha is the name for the set of uppercase and lowercase letters. The following two patterns arealmost the same: [A-Za-z] [[:alpha:]] The difference is that the alpha character class also includes accented characters like è. If you match data that contains nonASCII characters, the named character classes are more general than trying to name the characters explicitly. There are also backslash sequences that are shorthand for some of the named character classes. The following patterns to match digits are equivalent: [0-9] [[:digit:]] \d The following patterns match space-like characters including backspace, form feed, newline, carriage return, tag, and vertical tab: [ \b\f\n\r\t\v] [[:space:]] \s The named character classes and the associated backslash sequence are listed in Table 11-3 on page 156. You can use character classes in combination with other characters or character classes inside a character set definition. The following patterns match letters, digits, and underscore: [[:digit:][:alpha:]_] [\d[:alpha:]_] [[:alnum:]_] \w Note that \d, \s and \w can be used either inside or outside character sets. When used outside a bracketed expression, they form their own character set. There are also \D, \S, and \W, which are the complement of\d, \s, and \w. These escapes (i.e., \D for not-a-digit) cannot be used inside a bracketed character set. There are two special character classes, [[:<:] and [[:>:]], that match the beginning and end of a word, respectively. A word is defined as one or more characters that match \w. Nongreedy Quantifiers This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The *, +, and ? characters are quantifiers that specify repetition. By default these match as many characters as possible, which is called greedy matching. A nongreedy match will match as few characters as possible. You can specify nongreedy matching by putting a question mark after these quantifiers. Consider the pattern to match "one or more of not-a-newline followed by a newline." The not-a-newline must be explicit with the greedy quantifier, as in: [^\n]+\n Otherwise, if the pattern were just .+\n then the "." could well match newlines, so the pattern would greedily consume everything until the very last newline in the input. A nongreedy match would be satisfied with the very first newline instead: .+?\n By using the nongreedy quantifier we've cut the pattern from eight characters to five. Another example that is shorter with a nongreedy quantifier is the HTML example from page 148. The following pattern also matches everything between <td> and </td>: <td>(.*?)</td> Even ? can be made nongreedy, ??, which means it prefers to match zero instead of one. This only makes sense inside the context of a larger pattern. Send me email if you have a compelling example for it! Bound Quantifiers The {m,n} syntax is a quantifier that means match at leastm and at most n of the previous matching item. There are two variations on this syntax. A simple {m} means match exactly m of the previous matching item. A{m,} means match m or more of the previous matching item. All of these can be made nongreedy by adding a ? after them. Back References A back reference is a feature you cannot easily get with basic regular expressions. A back reference matches the value of a subpattern captured with parentheses. If you have several sets of parentheses you can refer back to different captured expressions with \1, \2, and so on. You count by left parentheses to determine the reference. For example, suppose you want to match a quoted string, where you can use either single or double quotes. You need to use an alternation of two patterns to match strings that are enclosed in double quotes or in single quotes: ("[^"]*"|'[^']*') With a back reference, \1, the pattern becomes simpler: ('|").*?\1 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Than The first set of parenthesis matches the leading quote, and then the \1 refers back to that particular quote character. The nongreedy quantifier ensures that the pattern matches up to the first occurrence of the matching quote. Look-ahead Look-ahead patterns are subexpressions that are matched but do not consume any of the input. They act like constraints on the rest of the pattern, and they typically occur at the end of your pattern. A positive look-ahead causes the pattern to match if it also matches. A negative look-ahead causes the pattern to match if it would not match. These constraints make more sense in the context of matching variables and in regular expression substitutions done with the regsub command. For example, the following pattern matches a filename that begins withA and ends with .txt ^A.*\.txt$ The next version of the pattern adds parentheses to group the file name suffix. ^A.*(\.txt$) The parentheses are not strictly necessary, but they are introduced so that we can compare the pattern to one that uses look-ahead. A version of the pattern that uses look-ahead looks like this: ^A.*(?=\.txt$) The pattern with the look-ahead constraint matches only the part of the filename before the .txt, but only if the .txt is present. In other words, the .txt is not consumed by the match. This is visible in the value of the matching variables used with the regexp command. It would also affect the substitutions done in the regsub command. There is negative look-ahead too. The following pattern matches a filename that begins with A and does not end with.txt. ^A.*(?!\.txt$) Writing this pattern without negative look-ahead is awkward. Character Codes The \nn and \mmm syntax, where n and m are digits, can also mean an 8-bit character code corresponding to the octal valuenn or mmm. This has priority over a back reference. However, I just wouldn't use this notation for character codes. Instead, use the Unicode escape sequence, \unnnn, which specifies a 16-bit value. The\xnn sequence also specifies an 8-bit character code. Unfortunately, the\x escape consumes all hex digits after it (not just two!) and then truncates the hexadecimal value down to 8 bits. This misfeature of \x is not considered a bug and will probably not change even in future versions of Tcl. The \Uyyyyyyyy syntax is reserved for 32-bit Unicode, but I don't expect to see that implemented anytime soon. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Collating Elements Collating elements are characters or long names for characters that you can use inside character sets. Currently, Tcl only has some long names for various ASCII punctuation characters. Potentially, it could support names for every Unicode character, but it doesn't because the mapping tables would be huge. This section will briefly mention the syntax so that you can understand it if you see it. But its usefulness is still limited. Within a bracketed expression, the following syntax is used to specify a collating element: [.identifier.] The identifier can be a character or a long name. The supported long names can be found in the generic/regc_locale.c file in the Tcl source code distribution. A few examples are shown below: [.c.] [.#.] [.number-sign.] Equivalence Classes An equivalence class is all characters that sort to the same position. This is another feature that has limited usefulness in the current version of Tcl. In Tcl, characters sort by their Unicode character value, so there are no equivalence classes that contain more than one character! However, you could imagine a character class for 'o', 'ò', and other accented versions of the letter o. The syntax for equivalence classes within bracketed expressions is: [=char=] where char is any one of the characters in the character class. This syntax is valid only inside a character class definition. Newline Sensitive Matching By default, the newline character is just an ordinary character to the matching engine. You can make the newline character special with two options: lineanchor and linestop. You can set these options with flags to theregexp and regsub Tcl commands, or you can use the embedded options described later in Table 11-5 on page 157. The lineanchor option makes the ^ and $ anchors work relative to newlines. The ^ matches immediately after a newline, and$ matches immediately before a newline. These anchors continue to match the very beginning and end of the input, too. With or without the lineanchor option, you can use \A and \Z to match the beginning and end of the string. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than The linestop option prevents . (i.e., period) and character sets that begin with^ from matching a newline character. In other words, unless you explicitly include \n in your pattern, it will not match across newlines. Embedded Options You can start a pattern with embedded options to turn on or off case sensitivity, newline sensitivity, and expanded syntax, which is explained in the next section. You can also switch from advanced regular expressions to a literal string, or to older forms of regular expressions. The syntax is a leading: (?chars) where chars is any number of option characters. The option characters are listed inTable 11-5 on page 157. Expanded Syntax Expanded syntax lets you include comments and extra white space in your patterns. This can greatly improve the readability of complex patterns. Expanded syntax is turned on with a regexp command option or an embedded option. Comments start with a # and run until the end of line. Extra white space and comments can occur anywhere except inside bracketed expressions (i.e., character sets) or within multicharacter syntax elements like (?=. When you are in expanded mode, you can turn off the comment character or include an explicit space by preceding them with a backslash. Example 11-1 shows a pattern to match URLs. The leading (?x) turns on expanded syntax. The whole pattern is grouped in curly braces to hide it from Tcl. This example is considered again in more detail in Example 11-3 on page 159: Example 11-1 Expanded regular expressions allow comments regexp {(?x) # A pattern to match URLS ([^:]+): # The protocol before the initial colon //([^:/]+) # The server name (:([0-9]+))? # The optional port number (/.*) # The trailing pathname } $input [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Syntax Summary Table 11-1 summarizes the syntax of regular expressions available in all versions of Tcl: Table 11-1. Basic regular expression syntax . Matches any character. * Matches zero or more instances of the previous pattern item. + Matches one or more instances of the previous pattern item. ? Matches zero or one instances of the previous pattern item. () Groups a subpattern. The repetition and alternation operators apply to the preceding subpattern. | Alternation. [] Delimit a set of characters. Ranges are specified as [x-y]. If the first character in the set is^, then there is a match if the remaining characters in the set are not present. ^ Anchor the pattern to the beginning of the string. Only when first. $ Anchor the pattern to the end of the string. Only when last. Advanced regular expressions, which were introduced in Tcl 8.1, add more syntax that is summarized in Table 11-2: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 11-2. Additional advanced regular expression syntax {m} Matches m instances of the previous pattern item. {m}? Matches m instances of the previous pattern item. Nongreedy. {m,} Matches m or more instances of the previous pattern item. {m,}? Matches m or more instances of the previous pattern item. Nongreedy. {m,n} Matches m through n instances of the previous pattern item. {m,n}? Matches m through n instances of the previous pattern item. Nongreedy. *? Matches zero or more instances of the previous pattern item. Nongreedy. +? Matches one or more instances of the previous pattern item. Nongreedy. ?? Matches zero or one instances of the previous pattern item. Nongreedy. (?:re) Groups a subpattern, re, but does not capture the result. (?=re) Positive look-ahead. Matches the point where re begins. (?!re) Negative look-ahead. Matches the point wherere does not begin. (?abc) Embedded options, where abc is any number of option letters listed in Table 11-5. \c One of many backslash escapes listed inTable 11-4. [: :] Delimits a character class within a bracketed expression. SeeTable 11-3. [. .] Delimits a collating element within a bracketed expression. [= =] Delimits an equivalence class within a bracketed expression. Table 11-3 lists the named character classes defined in advanced regular expressions and their associated backslash sequences, if any. Character class names are valid inside bracketed character sets with the [:class:] syntax. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 11-3. Character classes alnum Upper and lower case letters and digits. alpha Upper and lower case letters. blank Space and tab. cntrl Control characters: \u0001 through \u001F. digit The digits zero through nine. Also\d. graph Printing characters that are not incntrl or space. lower Lowercase letters. print The same as alnum. punct Punctuation characters. space Space, newline, carriage return, tab, vertical tab, form feed. Also\s. upper Uppercase letters. xdigit Hexadecimal digits: zero through nine, a-f, A-F. Table 11-4 lists backslash sequences supported in Tcl 8.1. Table 11-4. Backslash escapes in regular expressions \a Alert, or "bell", character. \A Matches only at the beginning of the string. \b Backspace character, \u0008. \B Synonym for backslash. \cX Control-X. \d Digits. Same as [[:digit:]] \D Not a digit. Same as[^[:digit:]] \e Escape character, \u001B. \f Form feed, \u000C. \m Matches the beginning of a word. \M Matches the end of a word. \n Newline, \u000A. \r Carriage return, \u000D. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks \s Space. Same as [[:space:]] \S Not a space. Same as[^[:space:]] \t Horizontal tab, \u0009. \uXXXX A 16-bit Unicode character code. \v Vertical tab, \u000B. \w Letters, digit, and underscore. Same as [[:alnum:]_] \W Not a letter, digit, or underscore. Same as[^[:alnum:]_] \xhh An 8-bit hexadecimal character code. Consumes all hex digits after\x. \y Matches the beginning or end of a word. \Y Matches a point that is not the beginning or end of a word. \Z Matches the end of the string. \0 NULL, \u0000 \x Where x is a digit, this is a back-reference. \xy Where x and y are digits, either a decimal back-reference, or an 8-bit octal character code. \xyz Where x, y and z are digits, either a decimal back-reference or an 8-bit octal character code. Table 11-5 lists the embedded option characters used with the(?abc) syntax. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Table 11-5. Embedded option characters used with the (?x) syntax b The rest of the pattern is a basic regular expression (a lavi or grep). c Case sensitive matching. This is the default. e The rest of the pattern is an extended regular expression (a la Tcl 8.0). i Case insensitive matching. m Synonym for the n option. n Newline sensitive matching . Bothlineanchor and linestop mode. p Partial newline sensitive matching. Only linestop mode. q The rest of the pattern is a literal string. s No newline sensitivity. This is the default. t Tight syntax; no embedded comments. This is the default. w Inverse partial newline-sensitive matching. Only lineanchor mode. x Expanded syntax with embedded white space and comments. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The regexp Command The regexp command provides direct access to the regular expression matcher. Not only does it tell you whether a string matches a pattern, it can also extract one or more matching substrings. The return value is 1 if some part of the string matches the pattern; it is 0 otherwise. Its syntax is: regexp ?flags? pattern string ?match sub1 sub2...? The flags are described in Table 11-6: Table 11-6. Options to the regexp command -nocase Lowercase characters in pattern can match either lowercase or uppercase letters instring. -indices The match variables each contain a pair of numbers that are in indices delimiting the match within string. Otherwise, the matching string itself is copied into the match variables. -expanded The pattern uses the expanded syntax discussed on page 154. -line The same as specifying both-lineanchor and -linestop. -lineanchor Change the behavior of ^ and $ so they are line-oriented as discussed on page 153. -linestop Change matching so that. and character classes do not match newlines as discussed on page 153. -about Useful for debugging. It returns information about the pattern instead of trying to match it against the input. -- Signals the end of the options. You must use this if your pattern begins with-. The pattern argument is a regular expression as described earlier. Ifstring matches pattern, then regexp stores the results of the match in the variables provided. These match variables are optional. If present, match is set to the part of the string that matched the pattern. The remaining variables are set to the substrings of string that matched the corresponding subpatterns in pattern. The correspondence is based on the order of left parentheses in the pattern to avoid ambiguities that can arise from nested subpatterns. Example 11-2 uses regexp to pick the hostname out of the DISPLAY environment variable, which has the form: hostname:display.screen Example 11-2 Using regular expressions to parse a string set env(DISPLAY) sage:0.1 regexp {([^:]*):} $env(DISPLAY) match host => 1 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set match => sage: set host => sage The pattern involves a complementary set, [^:], to match anything except a colon. It uses repetition,*, to repeat that zero or more times. It groups that part into a subexpression with parentheses. The literal colon ensures that the DISPLAY value matches the format we expect. The part of the string that matches the complete pattern is stored into the match variable. The part that matches the subpattern is stored intohost. The whole pattern has been grouped with braces to quote the square brackets. Without braces it would be: regexp (\[^:\]*): $env(DISPLAY) match host With advanced regular expressions the nongreedy quantifier *? can replace the complementary set: regexp (.*?): $env(DISPLAY) match host This is quite a powerful statement, and it is efficient. If we had only had the string command to work with, we would have needed to resort to the following, which takes roughly twice as long to interpret: set i [string first : $env(DISPLAY)] if {$i >= 0} { set host [string range $env(DISPLAY) 0 [expr $i-1]] } A Pattern to Match URLs Example 11-3 demonstrates a pattern with several subpatterns that extract the different parts of a URL. There are lots of subpatterns, and you can determine which match variable is associated with which subpattern by counting the left parenthesis. The pattern will be discussed in more detail after the example: Example 11-3 A pattern to match URLs set url http://www.beedub.com:80/index.html regexp {([^:]+)://([^:/]+)(:([0-9]+))?(/.*)} $url \ match protocol server x port path => 1 set match => http://www.beedub.com:80/index.html set protocol => http set server => www.beedub.com set x => :80 set port => 80 set path => /index.html This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Let's look at the pattern one piece at a time. The first part looks for the protocol, which is separated by a colon from the rest of the URL. The first part of the pattern is one or more characters that are not a colon, followed by a colon. This matches the http: part of the URL: [^:]+: Using nongreedy +? quantifier, you could also write that as: .+?: The next part of the pattern looks for the server name, which comes after two slashes. The server name is followed either by a colon and a port number, or by a slash. The pattern uses a complementary set that specifies one or more characters that are not a colon or a slash. This matches the //www.beedub.com part of the URL: //[^:/]+ The port number is optional, so a subpattern is delimited with parentheses and followed by a question mark. An additional set of parentheses are added to capture the port number without the leading colon. This matches the :80 part of the URL: (:([0-9]+))? The last part of the pattern is everything else, starting with a slash. This matches the /index.html part of the URL: /.* Use subpatterns to parse strings. To make this pattern really useful, we delimit several subpatterns with parentheses: ([^:]+)://([^:/]+)(:([0-9]+))?(/.*) These parentheses do not change the way the pattern matches. Only the optional port number really needs the parentheses in this example. However, the regexp command gives us access to the strings that match these subpatterns. In one stepregexp can test for a valid URL and divide it into the protocol part, the server, the port, and the trailing path. The parentheses around the port number include the : before the digits. We've used a dummy variable that gets the : and the port number, and another match variable that just gets the port number. By using noncapturing parentheses in advanced regular expressions, we can eliminate the unused match variable. We can also replace both complementary character sets with a nongreedy .+? match. Example 11-4 shows this variation: Example 11-4 An advanced regular expression to match URLs set url http://www.beedub.com:80/book/ regexp {(.+?)://(.+?)(?::([0-9]+))?(/.*)$} $url \ match protocol server port path => 1 set match => http://www.beedub.com:80/book/ set protocol . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks => http set server => www.beedub.com set port => 80 set path => /book/ Bugs When Mixing Greedy and Non-Greedy Quantifiers If you have a regular expression pattern that uses both greedy and non-greedy quantifiers, then you can quickly run into trouble. The problem is that in complex cases there can be ambiguous ways to resolve the quantifiers. Unfortunately, what happens in practice is that Tcl tends to make all the quantifiers either greedy, or all of them non-greedy. Example 11-4 has a $ at the end to force the last greedy term to go to the end of the string. In theory, the greediness of the last subpattern should match all the characters out to the end of the string. In practice, Tcl makes all the quantifiers non-greedy, so the anchor is necessary to force the pattern to match to the end of the string. Sample Regular Expressions The table in this section lists regular expressions as you would use them in Tcl commands. Most are quoted with curly braces to turn off the special meaning of square brackets and dollar signs. Other patterns are grouped with double quotes and use backslash quoting because the patterns include backslash sequences like \n and \t. In Tcl 8.0 and earlier, these must be substituted by Tcl before theregexp command is called. In these cases, the equivalent advanced regular expression is also shown. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 11-7. Sample regular expressions {^[yY]} Begins with y or Y, as in a Yes answer. {^(yes|YES|Yes)$} Exactly "yes", "Yes", or "YES". {^[^ \t:\]+:} Begins with colon-delimited field that has no spaces or tabs. {^\S+?:} Same as above, using \S for "not space". "^\[ \t]*$" A string of all spaces or tabs. {(?n)^\s*$} A blank line using newline sensitive mode. "(\n|^)\[^\n\]*(\n|$)" A blank line, the hard way. {^[A-Za-z]+$} Only letters. {^[[:alpha:]]+$} Only letters, the Unicode way. {[A-Za-z0-9_]+} Letters, digits, and the underscore. {\w+} Letters, digits, and the underscore using \w. {[][${}\\]} The set of Tcl special characters: ] [ $ { } \ "\[^\n\]*\n" Everything up to a newline. {.*?\n} Everything up to a newline using nongreedy *? {\.} A period. {[][$^?+*()|\\]} The set of regular expression special characters: ][$^?+*()|\ <H1>(.*?)</H1> An H1 HTML tag. The subpattern matches the string between the tags. <!--.*?--> HTML comments. {[0-9a-hA-H][0-9a-hA-H]} 2 hex digits. {[[:xdigit:]]{2}} 2 hex digits, using advanced regular expressions. {\d{1,3}} 1 to 3 digits, using advanced regular expressions. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. The regsub Command The regsub command does string substitution based on pattern matching. It is very useful for processing your data. It can perform simple tasks like replacing sequences of spaces and tabs with a single space. It can perform complex data transforms, too, as described in the next section. Its syntax is: regsub ?switches? pattern string subspec varname The regsub command returns the number of matches and replacements, or 0 if there was no match.regsub copies string to varname, replacing occurrences of pattern with the substitution specified bysubspec. If the pattern does not match, thenstring is copied to varname without modification. The optional switches include: -all, which means to replace all occurrences of the pattern. Otherwise, only the first occurrence is replaced. The -nocase, -expanded, -line, -linestop, and -lineanchor switches are the same as inthe regexp command. They are described on page 158. The -- switch separates the pattern from the switches, which is necessary if your pattern begins with a -. The replacement pattern, subspec, can contain literal characters as well as the following special sequences: & is replaced with the string that matched the pattern. \x , where x is a number, is replaced with the string that matched the corresponding subpattern inpattern. The correspondence is based on the order of left parentheses in the pattern specification. The following replaces a user's home directory with a ~: regsub ^$env(HOME)/ $pathname ~/ newpath The following constructs a C compile command line given a filename: set file tclIO.c regsub {([^\.]*)\.c$} $file {cc -c & -o \1.o} ccCmd The matching pattern captures everything before the trailing .c in the file name. The & is replaced with the complete match, tclIO.c, and \1 is replaced with tclIO, which matches the pattern between the parentheses. The value assigned toccCmd is: cc -c tclIO.c -o tclIO.o We could execute that with: eval exec $ccCmd The following replaces sequences of multiple space characters with a single space: regsub -all {\s+} $string " " string It is perfectly safe to specify the same variable as the input value and the result. Even if there is no match on the pattern, the input string is copied into the output variable. The regsub command can count things for us. The following command counts the newlines in some text. In this case the substitution is not important: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set numLines [regsub -all \n $text {} ignore] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Transforming Data to Program with regsub One of the most powerful combinations of Tcl commands is regsub and subst. This section describes a few examples that useregsub to transform data into Tcl commands, and then use subst to replace those commands with a new version of the data. This technique is very efficient because it relies on two subsystems that are written in highly optimized C code: the regular expression engine and the Tcl parser. These examples are primarily written by Stephen Uhler. URL Decoding When a URL is transmitted over the network, it is encoded by replacing special characters with a %xx sequence, where xx is the hexadecimal code for the character. In addition, spaces are replaced with a plus (+). It would be tedious and very inefficient to scan a URL one character at a time with Tcl statements to undo this encoding. It would be more efficient to do this with a custom C program, but still very tedious. Instead, a combination of regsub and subst can efficiently decode the URL in just a few Tcl commands. Replacing the + with spaces requires quoting the + because it is the one-or-more special character in regular expressions: regsub -all {\+} $url { } url The %xx are replaced with aformat command that will generate the right character: regsub -all {%([0-9a-hA-H][0-9a-hA-H])} $url \ {[format %c 0x\1]} url The %c directive to format tells it to generate the character from a character code number. We force a hexadecimal interpretation with a leading 0x. Advanced regular expressions let us write the "2 hex digits" pattern a bit more cleanly: regsub -all {%([[:xdigit:]]{2})} $url \ {[format %c 0x\1]} url The resulting string is passed to subst to get the format commands substituted: set url [subst $url] For example, if the input is %7ewelch, the result of the regsub is: [format %c 0x7e]welch And then subst generates: ~welch Example 11-5 encapsulates this trick in the Url_Decode procedure. Example 11-5 The Url_Decode procedure . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks proc Url_Decode {url} { regsub -all {\+} $url { } url regsub -all {%([:xdigit:]]{2})} $url \ {[format %c 0x\1]} url return [subst $url] } CGI Argument Parsing Example 11-6 builds upon Url_Decode to decode the inputs to a CGI program that processes data from an HTML form. Each form element is identified by a name, and the value is URL encoded. All the names and encoded values are passed to the CGI program in the following format: name1=value1&name2=value2&name3=value3 Example 11-6 shows Cgi_List and Cgi_Query. Cgi_Query receives the form data from the standard input or theQUERY_STRING environment variable, depending on whether the form data is transmitted with a POST or GET request. These HTTP operations are described in detail in Chapter 17. Cgi_List uses split to get back a list of names and values, and then it decodes them withUrl_Decode. It returns a Tcl-friendly name, value list that you can either iterate through with a foreach command, or assign to an array witharray set: Example 11-6 The Cgi_List and Cgi_Query procedures proc Cgi_List {} { set query [Cgi_Query] regsub -all {\+} $query { } query set result {} foreach {x} [split $query &=] { lappend result [Url_Decode $x] } return $result } proc Cgi_Query {} { global env if {![info exists env(QUERY_STRING)] || [string length $env(QUERY_STRING)] == 0} { if {[info exists env(CONTENT_LENGTH)] && [string length $env(CONTENT_LENGTH)] != 0} { set query [read stdin $env(CONTENT_LENGTH)] } else { gets stdin query } set env(QUERY_STRING) $query set env(CONTENT_LENGTH) 0 } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks return $env(QUERY_STRING) } An HTML form can have several form elements with the same name, and this can result in more than one value for each name. If you blindly use array set to map the results of Cgi_List into an array, you will lose the repeated values.Example 11-7 shows Cgi_Parse and Cgi_Value that store the query data in a global cgi array. Cgi_Parse adds list structure whenever it finds a repeated form value. The globalcgilist array keeps a record of how many times a form value is repeated. The Cgi_Value procedure returns elements of the globalcgi array, or the empty string if the requested value is not present. Example 11-7 Cgi_Parse and Cgi_Value store query data in the cgi array proc Cgi_Parse {} { global cgi cgilist catch {unset cgi cgilist} set query [Cgi_Query] regsub -all {\+} $query { } query foreach {name value} [split $query &=] { set name [CgiDecode $name] if {[info exists cgilist($name)] && ($cgilist($name) == 1)} { # Add second value and create list structure set cgi($name) [list $cgi($name) \ [Url_Decode $value]] } elseif {[info exists cgi($name)]} { # Add additional list elements lappend cgi($name) [CgiDecode $value] } else { # Add first value without list structure set cgi($name) [CgiDecode $value] set cgilist($name) 0 ;# May need to listify } incr cgilist($name) } return [array names cgi] } proc Cgi_Value {key} { global cgi if {[info exists cgi($key)]} { return $cgi($key) } else { return {} } } proc Cgi_Length {key} { global cgilist if {[info exist cgilist($key)]} { return $cgilist($key) } else { return 0 } } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Decoding HTML Entities The next example is a decoder for HTML entities. In HTML, special characters are encoded as entities. If you want a literal< or > in your document, you encode them as the entities &lt; and &gt;, respectively, to avoid conflict with the<tag> syntax used in HTML. HTML syntax is briefly described in Chapter 3 on page 34. Characters with codes above 127 such as copyright © and egrave è are also encoded. There are named entities, such as &lt; for < and &egrave; for è. You can also use decimal-valued entities such as&#169; for ©. Finally, the trailing semicolon is optional, so &lt or &lt; can both be used to encode<. The entity decoder is similar to Url_Decode. In this case, however, we need to be more careful withsubst. The text passed to the decoder could contain special characters like a square bracket or dollar sign. With Url_Decode we can rely on those special characters being encoded as, for example, %24. Entity encoding is different (do not ask me why URLs and HTML have different encoding standards), and dollar signs and square brackets are not necessarily encoded. This requires an additional pass to quote these characters. This regsub puts a backslash in front of all the brackets, dollar signs, and backslashes. regsub -all {[][$\\]} $text {\\&} new The decimal encoding (e.g., &#169;) is also more awkward than the hexadecimal encoding used in URLs. We cannot force a decimal interpretation of a number in Tcl. In particular, if the entity has a leading zero (e.g., &#010;) then Tcl interprets the value (e.g.,010) as octal. The scan command is used to do a decimal interpretation. It scans into a temporary variable, andset is used to get that value: regsub -all {&#([0-9][0-9]?[0-9]?);?} $new \ {[format %c [scan \1 %d tmp; set tmp]]} new With advanced regular expressions, this could be written as follows using bound quantifiers to specify one to three digits: regsub -all {&#(\d{1,3});?} $new \ {[format %c [scan \1 %d tmp;set tmp]]} new The named entities are converted with an array that maps from the entity names to the special character. The only detail is that unknown entity names (e.g., &foobar;) are not converted. This mapping is done inside HtmlMapEntity, which guards against invalid entities. regsub -all {&([a-zA-Z]+)(;?)} $new \ {[HtmlMapEntity \1 \\\2 ]} new If the input text contained: [x &lt; y] then the regsub would transform this into: \[x [HtmlMapEntity lt \; ] y\] Finally, subst will result in: [x < y] Example 11-8 Html_DecodeEntity proc Html_DecodeEntity {text} { This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks if {![regexp & $text]} {return $text} regsub -all {[][$\\]} $text {\\&} new regsub -all {&#([0-9][0-9]?[0-9]?);?} $new {\ [format %c [scan \1 %d tmp;set tmp]]} new regsub -all {&([a-zA-Z]+)(;?)} $new \ {[HtmlMapEntity \1 \\\2 ]} new return [subst $new] } proc HtmlMapEntity {text {semi {}}} { global htmlEntityMap if {[info exist htmlEntityMap($text)]} { return $htmlEntityMap($text) } else { return $text$semi } } # Some of the htmlEntityMap array set htmlEntityMap { lt < gt > amp& aring \xe5 atilde \xe3 copy \xa9 ecirc \xea egrave \xe8 } A Simple HTML Parser The following example is the brainchild of Stephen Uhler. It uses regsub to transform HTML into a Tcl script. When it is evaluated the script calls a procedure to handle each tag in an HTML document. This provides a general framework for processing HTML. Different callback procedures can be applied to the tags to achieve different effects. For example, the html_library-0.3 package on the CD-ROM uses Html_Parse to display HTML in a Tk text widget. Example 11-9 Html_Parse proc Html_Parse {html cmd {start {}}} { # Map braces and backslashes into HTML entities regsub -all \{ $html {\&ob;} html regsub -all \} $html {\&cb;} html regsub -all {\\} $html {\&bsl;} html # This pattern matches the parts of an HTML tag set s" \t\r\n" ;# white space set exp <(/?)(\[^$s>]+)\[$s]*(\[^>]*)> # This generates a call to cmd with HTML tag parts # \1 is the leading /, if any # \2 is the HTML tag name This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks # \3 is the parameters to the tag, if any # The curly braces at either end group of all the text # after the HTML tag, which becomes the last arg to $cmd. set sub "\}\n$cmd {\\2} {\\1} {\\3} \{" regsub -all $exp $html $sub html # This balances the curly braces, # and calls $cmd with $start as a pseudo-tag # at the beginning and end of the script. eval "$cmd {$start} {} {} {$html}" eval "$cmd {$start} / {} {}" } The main regsub pattern can be written more simply with advanced regular expressions: set exp {<(/?)(\S+?)\s*(.*?)>} An example will help visualize the transformation. Given this HTML: <Title>My Home Page</Title> <Body bgcolor=white text=black> <H1>My Home</H1> This is my <b>home</b> page. and a call to Html_Parse that looks like this: Html_Parse $html {Render .text} hmstart then the generated program is this: Render .text {hmstart} {} {} {} Render .text {Title} {} {} {My Home Page} Render .text {Title} {/} {} { } Render .text {Body} {} {bgcolor=white text=black} { } Render .text {H1} {} {} {My Home} Render .text {H1} {/} {} { This is my } Render .text {b} {} {} {home} Render .text {b} {/} {} { page. } Render .text {hmstart} / {} {} One overall point to make about this example is the difference between using eval and subst with the generated script. The decoders shown in Examples 11-5 and 11-8 use subst to selectively replace encoded characters while ignoring the rest of the text. InHtml_Parse we must process all the text. The main trick is to replace the matching text (e.g., the HTML tag) with some Tcl code that ends in an open curly brace and starts with a close curly brace. This effectively groups all the unmatched text. When eval is used this way you must do something with any braces and backslashes in the unmatched text. Otherwise, the resulting script does not parse correctly. In this case, these special characters are encoded as HTML entities. We can afford to do this because the cmd that is called must deal with encoded entities already. It is not possible to quote these special characters with backslashes because all this text is inside curly braces, so no backslash substitution is performed. If you try that the backslashes will be seen by the cmd callback. Finally, I must admit that I am always surprised that this works: eval "$cmd {$start} {} {} {$html}" I always forget that $start and $html are substituted in spite of the braces. This is because double quotes are being used to group the This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks argument, so the quoting effect of braces is turned off. Stripping HTML Comments The Html_Parse procedure does not correctly handle HTML comments. The problem is that the syntax for HTML commands allows tags inside comments, so there can be > characters inside the comment. HTML comments are also used to hide Javascript inside pages, which can also contain >. We can fix this with a pass that eliminates the comments. The comment syntax is this: <!-- HTML comment, could contain <markup> --> Using nongreedy quantifiers, we can strip comments with a singleregsub: regsub -all <!--.*?--> $html {} html Using only greedy quantifiers, it is awkward to match the closing --> without getting stuck on embedded > characters, or without matching too much and going all the way to the end of the last comment. Time for another trick: regsub -all --> $html \x81 html This replaces all the end comment sequences with a single character that is not allowed in HTML. Now you can delete the comments like this: regsub -all "<!--\[^\x81\]*\x81" $html {} html [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Other Commands That Use Regular Expressions Several Tcl commands use regular expressions. lsearch takes a -regexp flag so that you can search for list items that match a regular expression. Thelsearch command is described on page 69. switch takes a -regexp flag, so you can branch based on a regular expression match instead of an exact match or astring match style match. The switch command is described on page 77. The Tk text widget can search its contents based on a regular expression match. Searching in the text widget is described on page 542. The Expect Tcl extension can match the output of a program with regular expressions.Expect is the subject of its own book, Exploring Expect (O'Reilly, 1995) by Don Libes. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 12. Script Libraries and Packages Collections of Tcl commands are kept in libraries and organized into packages. Tcl automatically loads libraries as an application uses their commands. Tcl commands discussed are: package, pkg_mkIndex, auto_mkindex, unknown, and tcl_findLibrary. Libraries group useful sets of Tcl procedures so that they can be used by multiple applications. For example, you could use any of the code examples that come with this book by creating a script library and then directing your application to check in that library for missing procedures. One way to structure a large application is to have a short main script and a library of support scripts. The advantage of this approach is that not all the Tcl code needs to be loaded to start the application. Applications start up quickly, and as new features are accessed, the code that implements them is loaded automatically. The Tcl package facility supports version numbers and has a provide/require model of use. Typically, each file in a library provides one package with a particular version number. Packages also work with shared object libraries that implement Tcl commands in compiled code, which are described in Chapter 47. A package can be provided by a combination of script files and object files. Applications specify which packages they require and the libraries are loaded automatically. The package facility is an alternative to the auto loading scheme used in earlier versions of Tcl. You can use either mechanism, and this chapter describes them both. If you create a package you may wish to use the namespace facility to avoid conflicts between procedures and global variables used in different packages. Namespaces are the topic of Chapter 14. Before Tcl 8.0 you had to use your own conventions to avoid conflicts. This chapter explains a simple coding convention for large Tcl programs. I use this convention in exmh, a mail user interface that has grown from about 2,000 to over 35,000 lines of Tcl code. A majority of the code has been contributed by the exmh user community. Such growth might not have been possible without coding conventions. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Locating Packages: The auto_path Variable The package facility assumes that Tcl libraries are kept in well-known directories. The list of well-known directories is kept in the auto_path Tcl variable. This is initialized by tclsh and wish to include the Tcl script library directory, the Tk script library directory (forwish), and the parent directory of the Tcl script library directory. For example, on my Macintosh auto_path is a list of these three directories: Disk:System Folder:Extensions:Tool Command Language:tcl8.4 Disk:System Folder:Extensions:Tool Command Language Disk:System Folder:Extensions:Tool Command Language:tk8.4 On my Windows 95 machine the auto_path lists these directories: c:\Program Files\Tcl\lib\Tcl8.4 c:\Program Files\Tcl\lib c:\Program Files\Tcl\lib\Tk8.4 On my UNIX workstation the auto_path lists these directories: /usr/local/tcl/lib/tcl8.4 /usr/local/tcl/lib /usr/local/tcl/lib/tk8.4 The package facility searches these directories and their subdirectories for packages. The easiest way to manage your own packages is to create a directory at the same level as the Tcl library: /usr/local/tcl/lib/welchbook Packages in this location, for example, will be found automatically because the auto_path list includes /usr/local/tcl/lib. You can also add directories to the auto_path explicitly: lappend auto_path directory One trick I often use is to put the directory containing the main script into theauto_path. The following command sets this up: lappend auto_path [file dirname [info script]] If your code is split into bin and lib directories, then scripts in thebin directory can add the adjacent lib directory to their auto_path with this command: lappend auto_path \ [file join [file dirname [info script]] ../lib] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Using Packages Each script file in a library declares what package it implements with the package provide command: package provide name version The name identifies the package, and theversion has a major.minor format. The convention is that the minor version number can change and the package implementation will still be compatible. If the package changes in an incompatible way, then the major version number should change. For example, Chapter 17 defines several procedures that use the HTTP network protocol. These includehttp::geturl, http::wait, and http::cleanup. The file that contains the procedures starts with this command: package provide http 2.4 Case is significant in package names. In particular, the package that comes with Tcl is named http — all lowercase. More than one file can contribute to the same package simply by specifying the same name and version. In addition, different versions of the same package can be kept in the same directory but in different files. An application specifies the packages it needs with the package require command: package require name ?version? ?-exact? If the version is left off, then the highest available version is loaded. Otherwise the highest version with the same major number is loaded. For example, if the client requires version 1.1, version 1.2 could be loaded if it exists, but versions 1.0 and 2.0 would not be loaded. You can restrict the package to a specific version with the -exact flag. If no matching version can be found, then thepackage require command raises an error. Loading Packages Automatically The package require command depends on an index to record which files implement which packages. The index must be maintained by you, your project librarian, or your system administrator when packages change. The index is created by the pkg_mkIndex command, which puts the index into a pkgIndex.tcl file in each library directory. The pkg_mkIndex command takes the name of a directory and one or moreglob patterns that specify files within that directory. File name patterns are described on page 122. The syntax is: pkg_mkIndex ?options? directory pattern ?pattern ...? For example: pkg_mkIndex /usr/local/lib/welchbook *.tcl pkg_mkIndex -lazy /usr/local/lib/Sybtcl *.so The pkg_mkIndex command sources or loads all the files matched by the pattern, detects what packages they provide, and computes the index. You should be aware of this behavior because it works well only for libraries. If the pkg_mkIndex command hangs or starts random applications, it is because it sourced an application file instead of a library file. The package index, pkgIndex.tcl, is sourced in response to apackage require command. The index instructs the package loading mechanism how to define the package. By default, source or load commands are specified so that packages are defined immediately as a side effect of This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks package require. This is called direct loading. However, the original package index system used adeferred loading scheme layered on the auto_load mechanism and the unknown command hook, which is described on page 178. If you want deferred loading, use the-lazy option to pkg_mkIndex. The default behavior of pkg_mkIndex switched from -lazy to -direct in Tcl 8.3. The pkg_mkIndex options are summarized in Table 12-1. Table 12-1. Options to the pkg_mkIndex command -direct Generates an index with source and load commands in it. This results in packages being loaded directly as a result of package require. This is the default starting with Tcl 8.3. -lazy Generates an index that populates the auto_index array for deferred loading of commands. This behavior was the default prior to Tcl 8.3. -load Dynamically loads packages that match pattern into the slave interpreter used to compute the index. A common reason to pattern need this is with the tcbload package needed to load .tbc files compiled with TclPro Compiler. -verbose Displays the name of each file processed and any errors that occur. Packages Implemented in C Code The files in a library can be either script files that define Tcl procedures or binary files in shared library format that define Tcl commands in compiled code (i.e., a Dynamic Link Library (DLL)). Chapter 47 describes how to implement Tcl commands in C. There is a C API to the package facility that you use to declare the package name for your commands. This is shown in Example 47-1 on page 698. Chapter 37 also describes the Tcl load command that is used instead of source to link in shared libraries. The pkg_mkIndex command also handles shared libraries: pkg_mkIndex directory *.tcl *.so *.shlib *.dll In this example, .so, .shlib, and .dll are file suffixes for shared libraries on UNIX, Macintosh, and Windows systems, respectively. You can have packages that have some of their commands implemented in C, and some implemented as Tcl procedures. The script files and the shared library must simply declare that they implement the same package. The pkg_mkIndex procedure will detect this and set up the auto_index, so some commands are defined by sourcing scripts, and some are defined by loading shared libraries. If your file servers support more than one machine architecture, such as Solaris and Linux systems, you probably keep the shared library files in machine-specific directories. In this case the auto_path should also list the machine-specific directory so that the shared libraries there can be loaded automatically. If your system administrator configured the Tcl installation properly, this should already be set up. If not, or you have your shared libraries in a nonstandard place, you must append the location to the auto_path variable. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Summary of Package Loading The basic structure of package loading works like this: An application does a package require command. If the package is already loaded, the command just returns the version number of the already loaded package. If is not loaded, the following steps occur. The package facility checks to see if it knows about the package. If it does, then it runs the Tcl scripts registered with the package ifneeded command. These commands either load the package or set it up to be loaded automatically when its commands are first used. If the package is unknown, the tclPkgUnknown procedure is called to find it. Actually, you can specify what procedure to call to do the lookup with the package unknown command, but the standard one istclPkgUnknown. The tclPkgUnknown procedure looks through the auto_path directories and their subdirectories forpkgIndex.tcl files. It sources those to build an internal database of packages and version information. The pkgIndex.tcl files contain calls to package ifneeded that specify what to do to define the package. You can use the pkg_mkIndex command to create your pkgIndex.tcl files, or you can create them by hand. In the case of deferred package loading, the tclPkgSetup procedure defines the auto_index array to contain the correct source or load commands to define each command in the package. Automatic loading and theauto_index array are described in more detail later. As you can see, there are several levels of processing involved in finding packages. The system is flexible enough that you can change the way packages are located and how packages are loaded. The -lazy scenario is complicated because it uses the delayed loading of source code that is described in the next section. Using the -direct flag to pkg_mkIndex simplifies the situation. In any case, it all boils down to three key steps: Use pkg_mkIndex to maintain your index files. Decide at this time whether or not to use direct or lazy package loading. Put the appropriate package require and package provide commands in your code. Ensure that your library directories, or their parent directories, are listed in theauto_path variable. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The package Command The package command has several operations that are used primarily by thepkg_mkIndex procedure and the automatic loading facility. These operations are summarized in Table 12-2. Table 12-2. The package command package forget package Deletes registration information for package. package ifneeded package ?command? Queries or sets the command used to set up automatic loading of a package. package names Returns the set of registered packages. package provide package version Declares that a script file defines commands forpackage with the given version. package present package Equivalent to package require, except that no attempt to load the package is made if it is not loaded. ?version? ?-exact? package require package Declares that a script uses package. The -exact flag specifies that the exact version must be loaded. ?version? ?-exact? Otherwise, the highest matching version is loaded. package unknown ?command? Queries or sets the command used to locate packages. package vcompare v1 v2 Compares version v1 and v2. Returns 0 if they are equal, -1 ifv1 is less than v2, or 1 if v1 is greater than v2. package versions package Returns which versions of the package are registered. package vsatisfies v1 v2 Returns 1 if v1 is greater or equal to v2 and still has the same major version number. Otherwise returns 0. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Libraries Based on the tclIndex File You can create libraries without using the package command. The basic idea is that a directory has a library of script files, and an index of the Tcl commands defined in the library is kept in a tclIndex file. The drawback is that versions are not supported and you may need to adjust the auto_path to list your library directory. The main advantage of this approach is that this mechanism has been part of Tcl since the earliest versions. If you currently maintain a library using tclIndex files, it will still work. You must generate the index that records what procedures are defined in the library. The auto_mkindex procedure creates the index, which is stored in a file named tclIndex that is kept in the script library directory. (Watch out for the difference in capitalization betweenauto_mk index and pkg_mkIndex!) Suppose all the examples from this book are in the directory/usr/local/tcl/welchbook. You can make the examples into a script library by creating the tclIndex file: auto_mkindex /usr/local/tcl/welchbook *.tcl You will need to update the tclIndex file if you add procedures or change any of their names. A conservative approach to this is shown in the next example. It is conservative because it re-creates the index if anything in the library has changed since the tclIndex file was last generated, whether or not the change added or removed a Tcl procedure. Example 12-1 Maintaining a tclIndex file proc Library_UpdateIndex { libdir } { set index [file join $libdir tclIndex] if {![file exists $index]} { set doit 1 } else { set age [file mtime $index] set doit 0 # Changes to directory may mean files were deleted if {[file mtime $libdir] > $age} { set doit 1 } else { # Check each file for modification foreach file [glob [file join $libdir *.tcl]] { if {[file mtime $file] > $age} { set doit 1 break } } } } if { $doit } { auto_mkindex $libdir *.tcl } } . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The auto_path variable contains a list of directories to search for unknown commands. To continue our example, you can make the procedures in the book examples available by putting this command at the beginning of your scripts: lappend auto_path /usr/local/tcl/welchbook This has no effect if you have not created the tclIndex file. If you want to be extra careful, you can callLibrary_UpdateIndex. This will update the index if you add new things to the library. lappend auto_path /usr/local/tcl/welchbook Library_UpdateIndex /usr/local/tcl/welchbook This will not work if there is no tclIndex file at all because Tcl won't be able to find the implementation ofLibrary_UpdateIndex. Once the tclIndex has been created for the first time, then this will ensure that any new procedures added to the library will be installed into tclIndex. In practice, if you want this sort of automatic update, it is wise to include something like the Library_UpdateIndex procedure directly into your application as opposed to loading it from the library it is supposed to be maintaining. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The unknown Command The unknown command implements automatic loading of Tcl commands. Whenever the Tcl interpreter encounters a command that it does not know about, it calls the unknown command with the name of the missing command. Theunknown command is implemented in Tcl, so you are free to provide your own mechanism to handle unknown commands. This chapter describes the behavior of the default implementation of unknown, which can be found in theinit.tcl file in the Tcl library. The info library command returns the location of the library. How Auto Loading Works The unknown command uses an array named auto_index. One element of the array is defined for each procedure that can be automatically loaded. The auto_index array is initialized by the package mechanism or bytclIndex files. The value of an auto_index element is a command that defines the procedure. Typical commands are: source [file join $dir bind_ui.tcl] load [file join $dir mime.so] Mime The $dir gets substituted with the name of the directory that contains the library file, so the result is asource or load command that defines the missing Tcl command. The substitution is done with eval, so you could initializeauto_index with any commands at all. Example 12-2 is a simplified version of the code that reads the tclIndex file. Example 12-2 Loading a tclIndex file # This is a simplified part of the auto_load_index procedure. # Go through auto_path from back to front. set i [expr [llength $auto_path]-1] for {} {$i >= 0} {incr i -1} { set dir [lindex $auto_path $i] if [catch {open [file join $dir tclIndex]} f] { # No index continue } # eval the file as a script. Because eval is # used instead of source, an extra round of # substitutions is performed and $dir gets expanded # The real code checks for errors here. eval [read $f] close $f } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Disabling the Library Facility: auto_noload If you do not want the unknown procedure to try and load procedures, you can set theauto_noload variable to disable the mechanism: set auto_noload anything Auto loading is quite fast. I use it regularly on applications both large and small. A large application will start faster if you only need to load the code necessary to start it up. As you access more features of your application, the code will load automatically. Even a small application benefits from auto loading because it encourages you to keep commonly used code in procedure libraries. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Interactive Conveniences The unknown command provides a few other conveniences. These are used only when you are typing commands directly. They are disabled once execution enters a procedure or if the Tcl shell is not being used interactively. The convenience features are automatic execution of programs, command history, and command abbreviation. These options are tried, in order, if a command implementation cannot be loaded from a script library. Auto Execute The unknown procedure implements a second feature: automatic execution of external programs. This makes a Tcl shell behave more like other UNIX shells that are used to execute programs. The search for external programs is done using the standard PATH environment variable that is used by other shells to find programs. If you want to disable the feature all together, set the auto_noexec variable: set auto_noexec anything History The history facility described in Chapter 13 is implemented by the unknown procedure. Abbreviations If you type a unique prefix of a command, unknown recognizes it and executes the matching command for you. This is done after automatic program execution is attempted and history substitutions are performed. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Tcl Shell Library Environment Tcl searches for its script library directory when it starts up. In early versions of Tcl you had to compile in the correct location, set a Windows registry value, or set the TCL_LIBRARY environment variable to the correct location. Recent versions of Tcl use a standard searching scheme to locate the script library. The search understands the standard installation and build environments for Tcl, and it should eliminate the need to use the TCL_LIBRARY environment variable. On Windows the search for the library used to depend on registry values, but this has also been discontinued in favor of a standard search. In summary, "it should just work." However, this section explains how Tcl finds its script library so that you can troubleshoot problems. Locating the Tcl Script Library The default library location is defined when you configure the source distribution, which is explained on page 732. At this time an initial value for the auto_path variable is defined. (This default value appears intcl_pkgPath, but changing this variable has no effect once Tcl has started. I just pretend tcl_pkgPath does not exist.) These values are just hints; Tcl may use other directories depending on what it finds in the file system. When Tcl starts up, it searches for a directory that contains its init.tcl startup script. You can short-circuit the search by defining the TCL_LIBRARY environment variable. If this is defined, Tcl uses it only for its script library directory. However, you should not need to define this with normal installations of Tcl 8.0.5 or later. In my environment I'm often using several different versions of Tcl for various applications and testing purposes, so setting TCL_LIBRARY is never correct for all possibilities. If I find myself setting this environment variable, I know something is wrong with my Tcl installations! The standard search starts with the default value that is compiled into Tcl (e.g., /usr/local/lib/tcl8.4.) After that, the following directories are examined for an init.tcl file. These example values assume Tcl version 8.4 and patch level 8.4.1: ../lib/tcl8.4 ../../lib/tcl8.4 ../library ../../tcl8.4.1/library ../../../tcl8.4.1/library The first two directories correspond to the standard installation directories, while the last three correspond to the standard build environment for Tcl or Tk. The first directory in the list that contains a valid init.tcl file becomes the Tcl script library. This directory location is saved in the tcl_library global variable, and it is also returned by theinfo library command. The primary thing defined by init.tcl is the implementation of the unknown procedure. It also initializes auto_path to contain $tcl_library and the parent directory of $tcl_library. There may be additional directories added to auto_path depending on the compiled in value oftcl_pkgPath. tcl_findLibrary This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A generalization of this search is implemented by tcl_findLibrary. This procedure is designed for use by extensions like Tk and [incr Tcl]. Of course, Tcl cannot use tcl_findLibrary itself because it is defined ininit.tcl! The tcl_findLibrary procedure searches relative to the location of the main program (e.g.,tclsh or wish) and assumes a standard installation or a standard build environment. It also supports an override by an environment variable, and it takes care of sourcing an initialization script. The usage of tcl_findLibrary is: tcl_findLibrary base version patch script enVar varName The base is the prefix of the script library directory name. Theversion is the main version number (e.g., "8.0"). The patch is the full patch level (e.g., "8.0.3"). The script is the initialization script to source from the directory. TheenVar names an environment variable that can be used to override the default search path. The varName is the name of a variable to set to name of the directory found bytcl_findLibrary. A side effect of tcl_findLibrary is to source the script from the directory. An example call is: tcl_findLibrary tk 8.0 8.0.3 tk.tcl TK_LIBRARY tk_library This call first checks to see whether TK_LIBRARY is defined in the environment. If so, it uses its value. Otherwise, it searches the following directories for a file named tk.tcl. It sources the script and sets thetk_library variable to the directory containing that file. The search is relative to the value returned by info nameofexecutable: ../lib/tk8.0 ../../lib/tk8.0 ../library ../../tk8.0.3/library ../../../tk8.0.3/library Tk also adds $tk_library to the end of auto_path, so the other script files in that directory are available to the application: lappend auto_path $tk_library [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Coding Style If you supply a package, you need to follow some simple coding conventions to make your library easier to use by other programmers. You can use the namespace facility introduced in Tcl 8.0. You can also use conventions to avoid name conflicts with other library packages and the main application. This section describes the conventions I developed before namespaces were added to Tcl. A Module Prefix for Procedure Names The first convention is to choose an identifying prefix for the procedures in your package. For example, the preferences package in Chapter 45 uses Pref as its prefix. All the procedures provided by the library begin withPref. This convention is extended to distinguish between private and exported procedures. An exported procedure has an underscore after its prefix, and it is acceptable to call this procedure from the main application or other library packages. Examples include Pref_Add, Pref_Init, and Pref_Dialog. A private procedure is meant for use only by the other procedures in the same package. Its name does not have the underscore. Examples include PrefDialogItem and PrefXres. This naming convention precludes casual names like doit, setup, layout, and so on. Without using namespaces, there is no way to hide procedure names, so you must maintain the naming convention for all procedures in a package. A Global Array for State Variables You should use the same prefix on the global variables used by your package. You can alter the capitalization; just keep the same prefix. I capitalize procedure names and use lowercase letters for variables. By sticking with the same prefix you identify what variables belong to the package and you avoid conflict with other packages. Collect state in a global or namespaced array. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks In general, I try to use a single global or namespaced array for a package (namespaces are discussed in Chapter 14). The array provides a convenient place to collect a set of related variables, much as a struct is used in C. For example, the preferences package uses the pref array to hold all its state information. It is also a good idea to keep the use of the array private. It is better coding practice to provide exported procedures than to let other modules access your data structures directly. This makes it easier to change the implementation of your package without affecting its clients. When choosing a namespace name, try to make it significant to your application. If you do need to export a few key variables from your module, use the underscore convention to distinguish exported variables. If you need more than one global variable, just stick with the prefix convention to avoid conflicts, or provide accessor functions instead. The Official Tcl Style Guide John Ousterhout has published two programming style guides, one for C programming known as The Engineering Manual and one for Tcl scripts known as The Style Guide. These describe details about file structure as well as naming conventions for modules, procedures, and variables. The Tcl Style Guide conventions use Tcl namespaces to separate packages. Namespaces automatically provide a way to avoid conflict between procedure names. Namespaces also support collections of variables without having to use arrays for grouping. You can find these style guides on the CD-ROM and also in ftp://ftp.tcl.tk/pub/tcl/doc. The Engineering Manual is distributed as a compressed tar file, engManual.tar.Z, that contains sample files as well as the main document.The Style Guide is distributed as styleGuide.ps (or .pdf). [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 13. Reflection and Debugging This chapter describes commands that give you a view into the interpreter. The history command and a simple debugger are useful during development and debugging. The info command provides a variety of information about the internal state of the Tcl interpreter. Thetime command measures the time it takes to execute a command. Tcl commands discussed are: clock, info, history, and time. Reflection provides feedback to a script about the internal state of the interpreter. This is useful in a variety of cases, from testing to see whether a variable exists to dumping the state of the interpreter. The info command provides lots of different information about the interpreter. The clock command returns the time, formats time values, does time calculations, and parses time strings. It is a great tool all by itself. It also provides high-resolution timer information for precise measurements. Interactive command history is the third topic of the chapter. The history facility can save you some typing if you spend a lot of time entering commands interactively. Debugging is the last topic. The old-fashioned approach of adding puts commands to your code is often quite useful. For tough problems, however, a real debugger is invaluable. The Tcl Dev Kit toolset from ActiveState include a high quality debugger and static code checker. The tkinspect program is an inspector that lets you look into the state of a Tk application. It can hook up to any Tk application dynamically, so it proves quite useful. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] The clock Command The clock command has facilities for getting the current time, formatting time values, and scanning printed time strings to get an integer time value. Table 13-1 summarizes the clock command: Table 13-1. The clock command clock clicks ?-milliseconds? A high resolution counter. The precision is milliseconds, if specified (Tcl 8.4), or a system-dependent value. clock format value ?-format str? Formats a clock value according to str. See Table 13-2. clock scan string ?-base clock? ?-gmt Parses date string and return seconds value. The clock value determines the date. boolean? clock seconds Returns the current time in seconds. The following command prints the current time: clock format [clock seconds] => Fri Nov 22 4:09:14 PM PST 2002 The clock seconds command returns the current time, in seconds since a starting epoch. Theclock format command formats an integer value into a date string. It takes an optional argument that controls the format. The format strings contains % keywords that are replaced with the year, month, day, date, hours, minutes, and seconds, in various formats. The default string is: %a %b %d %H:%M:%S %Z %Y Tables 13-2 summarizes the clock formatting strings: Table 13-2. clock format keywords %% Inserts a %. %a Abbreviated weekday name (Mon, Tue, etc.). %A Full weekday name (Monday, Tuesday, etc.). %b Abbreviated month name (Jan, Feb, etc.). %B Full month name. %c Locale specific date and time (e.g.,Nov 24 16:00:59 1996). %C First two digits of the four-digit year (19 or 20). This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks %d Day of month (01 – 31). %D Date as %m/%d/%y (e.g., 02/19/97). %e Day of month (1 – 31), no leading zeros. %h Abbreviated month name. %H Hour in 24-hour format (00 – 23). %I Hour in 12-hour format (01 – 12). %j Day of year (001 – 366). %k Hour in 24-hour format, without leading zeros (0 - 23). %l Hour in 12-hour format, without leading zeros (1 – 12). %m Month number (01 – 12). %M Minute (00 – 59). %n Inserts a newline. %p AM/PM indicator. %r Time as %I:%M:%S %p (e.g., 02:39:29 PM). %R Time as %H:%M (e.g., 14:39). %s Seconds since the epoch. %S Seconds (00 – 59). %t Inserts a tab. %T Time as %H:%M:%S (e.g., 14:34:29). %u Weekday number (Monday = 1, Sunday = 7). %U Week of year (00 – 52) when Sunday starts the week. %V Week of year according to ISO-8601 rules (Week 1 contains January 4). %w Weekday number (Sunday = 0). %W Week of year (00 – 52) when Monday starts the week. %x Locale specific date format (e.g., Feb 19 1997). %X Locale specific time format (e.g., 20:10:13). %y Year without century (00 – 99). %Y Year with century (e.g. 1997). %Z Time zone name. The clock clicks command returns the value of the system's highest resolution clock. The units of the clicks is milliseconds if-milliseconds is specified, otherwise it is undefined. The main use of this command is to measure the relative time of different performance tuning trials. The -milliseconds flag was added in Tcl 8.4.Example 13-1 shows how to calibrate the clicks value by counting the clicks per second over 10 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks seconds, which will vary from system to system: Example 13-1 Calculating clicks per second set t1 [clock clicks] after 10000 ;# See page 228 set t2 [clock clicks] puts "[expr ($t2 - $t1)/10] Clicks/second" => 1001313 Clicks/second The clock scan command parses a date string and returns a seconds value. The command handles a variety of date formats. If you leave off the year, the current year is assumed. Year 2000 Compliance Tcl implements the standard interpretation of two-digit year values, which is that 70–99 are 1970–1999, 00–69 are 2000–2069. Versions of Tcl before 8.0 did not properly deal with two-digit years in all cases. Note, however, that Tcl is limited by your system's time epoch and the number of bits in an integer. On Windows, Macintosh, and most UNIX systems, the clock epoch is January 1, 1970. A 32-bit integer can count enough seconds to reach forward into the year 2037, and backward to the year 1903. If you try to clock scan a date outside that range, Tcl will raise an error because the seconds counter will overflow or underflow. In this case, Tcl is just reflecting limitations of the underlying system. Some 64-bit systems (such as Solaris 8 64-bit) use 64-bit integers for the system clock, which Tcl 8.4 supports. This extends the recognized range into the billions of years. If you leave out a date, clock scan assumes the current date. You can also use the -base option to specify a date. The following example uses the current time as the base, which is redundant: clock scan "10:30:44 PM" -base [clock seconds] => 2931690644 The date parser allows these modifiers: year, month, fortnight (two weeks), week, day, hour, minute, second. You can put a positive or negative number in front of a modifier as a multiplier. For example: clock format [clock scan "10:30:44 PM 1 week"] => Fri Nov 29 10:30:44 PM PST 2002 clock format [clock scan "10:30:44 PM -1 week"] Fri Nov 15 10:30:44 PM PST 2002 You can also use tomorrow, yesterday, today, now, last, this, next, and ago, as modifiers. clock format [clock scan "3 years ago"] => Mon Nov 22 4:18:34 PM PST 1999 Both clock format and clock scan take a -gmt option that uses Greenwich Mean Time. Otherwise, the local time zone is used. clock format [clock seconds] -gmt true => Sat Nov 23 12:19:13 AM GMT 2002 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks clock format [clock seconds] -gmt false => Fri Nov 22 4:19:35 PM PST 2002 [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The info Command Table 13-3 summarizes the info command. The operations are described in more detail later. Table 13-3. The info command info args procedure A list of procedure's arguments. info body procedure The commands in the body of procedure. info cmdcount The number of commands executed so far. info commands ?pattern? A list of all commands, or those matchingpattern. Includes built-ins and Tcl procedures. info complete string True if string contains a complete Tcl command. info default proc arg var True if arg has a default parameter value in procedureproc. The default value is stored into var. info exists variable True if variable is defined. info functions ?pattern? A list of all math functions, or those matchingpattern. (Tcl 8.4) info globals ?pattern? A list of all global variables, or those matchingpattern. info hostname The name of the machine. This may be the empty string if networking is not initialized. info level The stack level of the current procedure, or 0 for the global scope. info level number A list of the command and its arguments at the specified level of the stack. info library The pathname of the Tcl library directory. info loaded ?interp? A list of the libraries loaded into the interpreter namedinterp, which defaults to the current one. info locals ?pattern? A list of all local variables, or those matchingpattern. info nameofexecutable The file name of the program (e.g., of tclsh or wish). info patchlevel The release patch level for Tcl. info procs ?pattern? A list of all Tcl procedures, or those that matchpattern. info script ?filename? The name of the file being processed, or the empty string. info sharedlibextension The file name suffix of shared libraries. info tclversion The version number of Tcl. info vars ?pattern? A list of all visible variables, or those matchingpattern. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Variables There are three categories of variables: local, global, and visible. Information about these categories is returned by thelocals, globals, and vars operations, respectively. The local variables include procedure arguments as well as locally defined variables. The global variables include all variables defined at the global scope. The visible variables include locals, plus any variables made visible via global or upvar commands. A pattern can be specified to limit the returned list of variables to those that match the pattern. The pattern is interpreted according to the rules of string match, which is described on page 53: info globals auto* => auto_index auto_noexec auto_path Namespaces, which are the topic of the next chapter, partition global variables into different scopes. You query the variables visible in a namespace with: info vars namespace::* Remember that a variable may not be defined yet even though a global or upvar command has declared it visible in the current scope. Use the info exists command to test whether a variable or an array element is defined or not. An example is shown on page 96. Procedures You can find out everything about a Tcl procedure with the args, body, and default operations. This is illustrated in the followingProc_Show example. The puts commands use the -nonewline flag because the newlines in the procedure body, if any, are retained: Example 13-2 Printing a procedure definition proc Proc_Show {{namepat *} {file stdout}} { foreach proc [info procs $namepat] { set space "" puts -nonewline $file "proc $proc {" foreach arg [info args $proc] { if [info default $proc $arg value] { puts -nonewline $file "$space{$arg $value}" } else { puts -nonewline $file $space$arg } set space " " } # Double quotes allow substitution # of [info body $proc] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks puts $file "} {[info body $proc]}" } } Example 13-3 is a more elaborate example of procedure introspection that comes from thedirect.tcl file, which is part of the Tcl Web Server described in Chapter 18. This code is used to map URL requests and the associated query data directly into Tcl procedure calls. This is discussed in more detail on page 262. The Web server collects Web form data into an array called form. Example 13-3 matches up elements of the form array with procedure arguments, and it collects extra elements into anargs parameter. If a form value is missing, then the default argument value or the empty string is used: Example 13-3 Mapping form data onto procedure arguments # cmd is the name of the procedure to invoke # form is an array containing form values set cmdOrig $cmd set params [info args $cmdOrig] # Match elements of the form array to parameters foreach arg $params { if {![info exists form($arg)]} { if {[info default $cmdOrig $arg value]} { lappend cmd $value } elseif {[string equal $arg "args"]} { set needargs yes } else { lappend cmd {} } } else { lappend cmd $form($arg) } } # If args is a parameter, then append the form data # that does not match other parameters as extra parameters if {[info exists needargs]} { foreach {name value} [array get form] { if {[lsearch $params $name] < 0} { lappend cmd $name $value } } } # Eval the command set code [catch $cmd result] The info commands operation returns a list of all commands, which includes both built-in commands defined in C and Tcl procedures. There is no operation that just returns the list of built-in commands. Example 13-4 finds the built-in commands by removing all the procedures from the list of commands. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Example 13-4 Finding built-in commands proc Command_Info {{pattern *}} { # Create a table of procedures for quick lookup foreach p [info procs $pattern] { set isproc($p) 1 } # Look for command not in the procedure table set result {} foreach c [info commands $pattern] { if {![info exists isproc($c)]} { lappend result $c } } return [lsort $result] } The Call Stack The info level operation returns information about the Tcl evaluation stack, orcall stack. The global level is numbered zero. A procedure called from the global level is at level one in the call stack. A procedure it calls is at level two, and so on. The info level command returns the current level number of the stack if no level number is specified. If a positive level number is specified (e.g., info level 3), then the command returns the procedure name and argument values at that level in the call stack. If a negative level is specified, then it is relative to the current call stack. Relative level -1 is the level of the current procedure's caller, and relative level 0 is the current procedure. The following example prints the call stack. The Call_trace procedure avoids printing information about itself by starting at one less than the current call stack level: Example 13-5 Getting a trace of the Tcl call stack proc Call_Trace {{file stdout}} { puts $file "Tcl Call Trace" for {set x [expr [info level]-1]} {$x > 0} {incr x -1} { puts $file "$x: [info level $x]" } } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Command Evaluation If you want to know how many Tcl commands are executed, use the info cmdcount command. This counts all commands, not just top-level commands. The counter is never reset, so you need to sample it before and after a test run if you want to know how many commands are executed during a test. Command tracing provides detailed information about the execution of commands. It is described along with variable tracing on page 193. The info complete operation figures out whether a string is a complete Tcl command. This is useful for command interpreters that need to wait until the user has typed in a complete Tcl command before passing it to eval. Example 13-6 defines Command_Process that gets a line of input and builds up a command. When the command is complete, the command is executed at the global scope. Command_Process takes two callbacks as arguments. The inCmd is evaluated to get the line of input, and theoutCmd is evaluated to display the results. Chapter 10 describes callbacks why the curly braces are used with eval as they are in this example: Example 13-6 A procedure to read and evaluate commands proc Command_Process {inCmd outCmd} { global command append command(line) [eval $inCmd] if {[info complete $command(line)]} { set code [catch {uplevel #0 $command(line)} result] eval $outCmd {$result $code} set command(line) {} } } proc Command_Read {{in stdin}} { if {[eof $in]} { if {$in != "stdin"} { close $in } return {} } return [gets $in] } proc Command_Display {file result code} { puts stdout $result } while {![eof stdin]} { Command_Process {Command_Read stdin} \ {Command_Display stdout} } Scripts and the Library This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The name of the current script file is returned with the info script command. For example, if you use thesource command to read commands from a file, then info script returns the name of that file if it is called during execution of the commands in that script. This is true even if the info script command is called from a procedure that is not defined in the script. Use info script to find related files. I often use info script to source or process files stored in the same directory as the script that is running. A few examples are shown Example in 13-7. Example 13-7 Using info script to find related files # Get the directory containing the current script. set dir [file dirname [info script]] # Source a file in the same directory source [file join $dir helper.tcl] # Add an adjacent script library directory to auto_path # The use of ../lib with file join is cross-platform safe. lappend auto_path [file join $dir ../lib] The pathname of the Tcl library is stored in the tcl_library variable, and it is also returned by theinfo library command. While you could put scripts into this directory, it might be better to have a separate directory and use the script library facility described in Chapter 12. This makes it easier to deal with new releases of Tcl and to package up your code if you want other sites to use it. Version Numbers Each Tcl release has a version number such as 7.4 or 8.0. This number is returned by the info tclversion command. If you want your script to run on a variety of Tcl releases, you may need to test the version number and take different actions in the case of incompatibilities between releases. The Tcl release cycle starts with one or two alpha and beta releases before the final release, and there may even be a patch release after that. The info patchlevel command returns a qualified version number, like 8.0b1 for the first beta release of 8.0. We switched from using "p" (e.g., 8.0p2) to a three-level scheme (e.g., 8.0.3) for patch releases. The patch level is zero for the final release (e.g., 8.2.0). In general, you should be prepared for feature changes during the beta cycle, but there should only be bug fixes in the patch releases. Another rule of thumb is that the Tcl script interface remains quite compatible between releases; feature additions are upward compatible. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Execution Environment The file name of the program being executed is returned with info nameofexecutable. This is more precise than the name in theargv0 variable, which could be a relative name or a name found in a command directory on your command search path. It is still possible for info nameofexecutable to return a relative pathname if the user runs your program as./foo, for example. The following construct always returns the absolute pathname of the current program. If info nameofexecutable returns an absolute pathname, then the value of the current directory is ignored. The pwd command is described on page 122: file join [pwd] [info nameofexecutable] A few operations support dynamic loading of shared libraries, which are described in Chapter 47. The info sharedlibextension returns the file name suffix of dynamic link libraries. The info loaded command returns a list of libraries that have been loaded into an interpreter. Multiple interpreters are described in Chapter 19. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Cross-Platform Support Tcl is designed so that you can write scripts that run unchanged on UNIX, Macintosh, and Windows platforms. In practice, you may need a small amount of code that is specific to a particular platform. You can find out information about the platform via the tcl_platform variable. This is an array with these elements defined: tcl_platform(platform) is one of unix, macintosh, or windows. tcl_platform(os) identifies the operating system. Examples includeMacOS, Solaris, Linux, Win32s (Windows 3.1 with the Win32 subsystem), Windows 95, Windows NT, and SunOS. tcl_platform(osVersion) gives the version number of the operating system. tcl_platform(machine) identifies the hardware. Examples includeppc (Power PC), 68k (68000 family), sparc, intel, mips, and alpha. tcl_platform(byteOrder) identifies the byte order of this machine and is one oflittleEndian or bigEndian. tcl_platform(wordSize) identifies the size of the native machine word in bytes. This was introduced in Tcl 8.4. tcl_platform(isWrapped) indicates that the application has been wrapped up into a single executable withTclPro Wrapper. This is not defined in normal circumstances. tcl_platform(user) gives the login name of the current user. tcl_platform(debug) indicates that Tcl was compiled with debugging symbols. tcl_platform(threaded) indicates that Tcl was compiled with thread support enabled. On some platforms a hostname is defined. If available, it is returned with theinfo hostname command. This command may return an empty string. One of the most significant areas affected by cross-platform portability is the file system and the way files are named. This topic is discussed on page 110. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Tracing Variables and Commands The trace command registers a command to be called whenever a variable is accessed, modified, or unset. Tcl 8.4 introduced an updated trace command which includes support for command tracing. The original (and still supported) form of the command applies only to variable traces: trace variable name ops command trace vdelete name ops command trace vinfo name The name is a Tcl variable name, which can be a simple variable, an array, or an array element. If a whole array is traced, the trace is invoked when any element is used according to ops. The ops argument is one or more of the letters r, for read traces, w, for write traces, u, for unset traces, and a for array traces. The command is executed when one of these events occurs. It is invoked as: command name1 name2 op The name1 argument is the variable or array name. Thename2 argument is the name of the array index, or null if the trace is on a simple variable. If there is an unset trace on an entire array and the array is unset, name2 is also null. The value of the variable is not passed to the procedure. The traced variable is one level up the Tcl call stack. The upvar, uplevel, or global commands need to be used to make the variable visible in the scope of command. These commands are described in more detail inChapter 7. A read trace is invoked before the value of the variable is returned, so if it changes the variable itself, the new value is returned. A write trace is called after the variable is modified. The unset trace is called after the variable is unset. The array trace, which was added in Tcl 8.4, is called before the array command (e.g., array names) is used on the variable. A variable trace is automatically deleted when the variable is unset. Command Tracing The new form of trace supports both variable and command tracing: trace add type name ops command trace remove type name ops command trace info type name The type is one of command, execution or variable. For command, ops is a list and may contain rename, to trace the renaming of a Tcl command, or delete, to trace the deletion of a command. Command tracing cannot be used to prevent the actual deletion of a command, it just receives the notification. No command traces are triggered when an interpreter is deleted. The command is invoked as: command oldName newName op For execution, the ops may be any of enter, leave, enterstep, and leavestep. enter invokes command immediately before the command name is executed, and leave will invoke command immediately following each execution.enterstep and leavestep are similar but they operate on the Tcl procedure name, invoking command for each Tcl command inside the procedure. In order to do this, they prevent the bytecode This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks compilation of that procedure. This allows you to create a simple debugger in pure Tcl. The enter and enterstep operations invoke command as: command command-string op The leave and leavestep operations invoke command as: command command-string code result op The command-string is the current command being executed, code is the result code of the execution andresult is the result string. Example 6-16 on page 84 illustrates the different result codes. For variable tracing, the ops may be one or more of read, write, unset, or array. This is an alternate way to set up the variable traces described earlier. Read-Only Variables Example 13-8 uses traces to implement a read-only variable. A variable is modified before the trace procedure is called, so theReadOnly variable is needed to preserve the original value. When a variable is unset, the traces are automatically removed, so the unset trace action reestablishes the trace explicitly. Note that the upvar alias (e.g., var) cannot be used to set up the trace. Instead,uplevel is used to create the trace in the original context of the variable. In general, essentially all traces are on global or namespace variables. Example 13-8 Tracing variables proc ReadOnlyVar {varName} { upvar 1 $varName var global ReadOnly set ReadOnly($varName) $var uplevel 1 [list trace variable $varName wu ReadOnlyTrace] } proc ReadOnlyTrace { varName index op } { global ReadOnly upvar 1 $varName var switch $op { w{ set var $ReadOnly($varName) } u{ set var $ReadOnly($varName) # Re-establish the trace using the true name uplevel 1 [list ReadOnlyVar $varName] } } } This example merely overrides the new value with the saved value. Another alternative is to raise an error with the error command. This will cause the command that modified the variable to return the error. Another common use of trace is to update a user interface widget in response to a variable change. Several of the Tk widgets have this feature built into them. . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks If more than one trace is set on a variable, then they are invoked in reverse order; the most recent trace is executed first. If there is a trace on an array and on an array element, then the trace on the array is invoked first. Creating an Array with Traces Example 13-9 uses an array trace to dynamically create array elements: Example 13-9 Creating array elements with array traces # make sure variable is an array set dynamic() {} trace variable dynamic r FixupDynamic proc FixupDynamic {name index op} { upvar 1 $name dynArray if {![info exists dynArray($index)]} { set dynArray($index) 0 } } Information about traces on a variable is returned with thevinfo option: trace vinfo dynamic => {r FixupDynamic} A trace is deleted with the vdelete option, which has the same form as thevariable option. The trace in the previous example can be removed with the following command: trace vdelete dynamic r FixupDynamic [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Interactive Command History Table 13-4. The history command history Short for history info with no count. history add command ?exec? Adds the command to the history list. Ifexec is specified, then execute the command. history change new ?event? Changes the command specified byevent to new in the command history. history event ?event? Returns the command specified byevent. history info ?count? Returns a formatted history list of the lastcount commands, or of all commands. history keep count Limits the history to the lastcount commands. history nextid Returns the number of the next event. history redo ?event? Repeats the specified command. The Tcl shell programs keep a log of the commands that you type by using a history facility. The log is controlled and accessed via the history command. The history facility uses the term event to mean an entry in its history log. The events are just commands, and they have an event ID that is their index in the log. You can also specify an event with a negative index that counts backwards from the end of the log. Event -1 is the previous event. Table 13-4 summarizes the Tcl history command. In the table, event defaults to -1. In practice you will want to take advantage of the ability to abbreviate the history options and even the name of the history command itself. For the command, you need to type a unique prefix, and this depends on what other commands are already defined. For the options, there are unique one-letter abbreviations for all of them. For example, you could reuse the last word of the previous command with [history w $]. This works because a $ that is not followed by alphanumerics or an open brace is treated as a literal$. Several of the history operations update the history list. They remove the actual history command and replace it with the command that resulted from the history operation. The event and redo operations all behave in this manner. This makes perfect sense because you would rather have the actual command in the history, instead of the history command used to retrieve the command. History Syntax Some extra syntax is supported when running interactively to make the history facility more convenient to use. Table 13-5 shows the special history syntax supported by tclsh and wish. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 13-5. Special history syntax !! Repeats the previous command. !n Repeats command number n.If n is negative it counts backward from the current command. The previous command is event -1. !prefix Repeats the last command that begins withprefix. !pattern Repeats the last command that matchespattern. ^old^new Globally replaces old with new in the last command. The next example shows how some of the history operations work: Example 13-10 Interactive history usage % set a 5 5 % set a [expr $a+7] 12 % history 1 set a 5 2 set a [expr $a+7] 3 history % !2 19 % !! 26 % ^7^13 39 % !h 1 set a 5 2 set a [expr $a+7] 3 history 4 set a [expr $a+7] 5 set a [expr $a+7] 6 set a [expr $a+13] 7 history A Comparison to C Shell History Syntax The history syntax shown in the previous example is simpler than the history syntax provided by the C shell. Not all of the history operations are supported with special syntax. The substitutions (using ^old^new) are performed globally on the previous command. This is different from the quick-history of the C shell. Instead, it is like the !:gs/old/new/ history command. So, for example, if the example had included^a^b in an attempt to set b to 39, an error would have occurred because the command would have usedb before it was defined: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set b [expr $b+7] If you want to improve the history syntax, you will need to modify the unknown command, which is where it is implemented. This command is discussed in more detail in Chapter 12. Here is the code from theunknown command that implements the extra history syntax. The main limitation in comparison with the C shell history syntax is that the ! substitutions are performed only when! is at the beginning of the command: Example 13-11 Implementing special history syntax # Excerpts from the standard unknown command # uplevel is used to run the command in the right context if {$name == "!!"} { set newcmd [history event] } elseif {[regexp {^!(.+)$} $name dummy event]} { set newcmd [history event $event] } elseif {[regexp {^\^([^^]*)\^([^^]*)\^?$} $name x old new]} { set newcmd [history event -1] catch {regsub -all -- $old $newcmd $new newcmd} } if {[info exists newcmd]} { history change $newcmd 0 return [uplevel $newcmd] } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Debugging The rapid turnaround with Tcl coding means that it is often sufficient to add a few puts statements to your script to gain some insight about its behavior. This solution doesn't scale too well, however. A slight improvement is to add a Debug procedure that can have its output controlled better. You can log the information to a file, or turn it off completely. In a Tk application, it is simple to create a text widget to hold the contents of the log so that you can view it from the application. Here is a simple Debug procedure. To enable it you need to set thedebug(enable) variable. To have its output go to your terminal, set debug(file) to stderr. Example 13-12 A Debug procedure proc Debug { args } { global debug if {![info exists debug(enabled)]} { # Default is to do nothing return } puts $debug(file) [join $args " "] } proc DebugOn {{file {}}} { global debug set debug(enabled) 1 if {[string length $file] == 0} { set debug(file) Stderr } else { if [catch {open $file w} fileID] { puts stderr "Cannot open $file: $fileID" set debug(file) stderr } else { puts stderr "Debug info to $file" set debug(file) $fileID } } } proc DebugOff {} { global debug if {[info exists debug(enabled)]} { unset debug(enabled) flush $debug(file) if {$debug(file) != "stderr" && $debug(file) != "stdout"} { close $debug(file) unset debug(file) } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Tcl Dev Kit Tcl Dev Kit is a commercial development environment for Tcl based on the originalTclPro created by Scriptics. TclPro was released to the open-source community in November 2001. ActiveState has enhanced Tcl Dev Kit with new tools and more features. The development [*] environment includes ActiveTcl , which is an extended Tcl platform that includes [incr Tcl], Expect, and TclX. These extensions and Tcl/Tk are distributed in source and binary form for Windows and a variety of UNIX platforms. More information is available at this URL: [*] ActiveTcl is a trademark of ActiveState Corporation. http://www.activestate.com/Tcl The current version of the Tcl Dev Kit contains these tools: Debugger with Coverage The Debugger provides a nice graphical user interface with all the features you expect from a traditional debugger. You can set breakpoints, single step, examine variables, and look at the call stack. It understands a subtle issue that can arise from using the update command: nested call stacks. It is possible to launch a new Tcl script as a side effect of the update command, which pushes the current state onto the execution stack. This shows up clearly in the debugger stack trace. It maintains project state, so it will remember breakpoint settings and other preference items between runs. One of the most interesting features is that it can debug remotely running applications. The debugger also has built-in code coverage and hotspot profiling analysis. I use it regularly to debug Tcl code running inside the Tcl Web Server. Checker The Checker is a static code checker. This is a real win for large program development. It examines every line of your program looking for syntax errors and dubious coding practices. It has detailed knowledge of Tcl, Tk, Expect, [incr Tcl], and TclX commands and validates your use of them. It checks that you call Tcl procedures with the correct number of arguments, and can cross-check large groups of Tcl files. It knows about changes between Tcl versions, and it can warn you about old code that needs to be updated. Compiler This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The Compiler is really just a reader and writer for the byte codes that the Tcl byte-code compiler generates internally. It lets you precompile scripts and save the results, and then load the byte-code later instead of raw source. This provides a great way to hide your source code, if that is important to you. It turns out to save less time than you might think, however. By the time it reads the file from disk, decodes it, and builds the necessary Tcl data structures, it is not much faster than reading a source file and compiling it on the fly. TclApp TclApp assembles a collection of Tcl scripts, data files, and a Tcl/Tk interpreter into Starkits and Starpacks, which are described in Chapter 22. TclApp provides a more friendly user interface than thesdx command line tool described in that Chapter. TheTcl Dev Kit comes with pre-built Starkit runtimes that include Metakit, Expect, [incr Tcl], and TclX. Tcl Service Manager The Tcl Service Manager helps you turn your Tcl application into a service for Windows NT/2000/XP. Services have to implement special OS interfaces that are not supported by tclsh or wish. You can create services that use the DLLs and scripts from an existing Tcl/Tk installation, or create stand alone services that have no external dependencies. Inspector The Inspector is an improved version of the tkinspect application that lets you look at the state of other Tk applications. It displays procedures, variables, and the Tk widget hierarchy. You can issue commands to another application to change variables or test out commands. This turns out to be a very useful way to debug Tk applications. The original tkinspect was written by Sam Shen. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Other Tools The Tcl community has built many interesting and useful tools to help your Tcl development. Only two of them are mentioned below, but you can find many more at the Tcl Resource Center: http://www.tcl.tk/resource/ The tkcon Console Tkcon is an enhanced Tk console application written purely in Tcl. It includes many useful interactive control features, and may be embedded in other Tcl applications. It was written by Jeff Hobbs and you can find it at: http://tkcon.sourceforge.net/ Critcl Critcl is a tool that lets you mix C code right into your Tcl scripts. When thecproc command encounters its code for the first time, it automatically compiles it with gcc and loads it into your application. This provides an easy way to recode small parts of your application in C to get a performance boost. It's home page is: http://www.equi4.com/critcl The bgerror Command When a Tcl script encounters an error during background processing, such as handling file events or during the command associated with a button, it signals the error by calling the bgerror procedure. A default implementation displays a dialog and gives you an opportunity to view the Tcl call stack at the point of the error. You can supply your own version of bgerror. For example, when my exmh mail application gets an error it offers to send mail to me with a few words of explanation from the user and a copy of the stack trace. I get interesting bug reports from all over the world! The bgerror command is called with one argument that is the error message. The global variableerrorInfo contains the stack trace information. There is an example tkerror implementation in the on-line sources associated with this book. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The tkerror Command The bgerror command used to be called tkerror. When event processing shifted from Tk into Tcl with Tcl 7.5 and Tk 4.1, the nametkerror was changed to bgerror. Backwards compatibility is provided so that iftkerror is defined, then tkerror is called instead of bgerror. I have run into problems with the compatibility setup and have found it more reliable to update my applications to use bgerror instead of tkerror. If you have an application that runs under either Tk 4.0 or Tk 4.1, you can simply define both: proc bgerror [info args tkerror] [info body tkerror] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Performance Tuning The time command measures the execution time of a Tcl command. It takes an optional parameter that is a repetition count: time {set a "Hello, World!"} 1000 => 28 microseconds per iteration If you need the result of the command being timed, use set to capture the result: puts $log "command: [time {set result [command]}]" An extensive benchmark suite that compares various Tcl versions is available at: http://wiki.tcl.tk/Tcl%20Benchmarks Time stamps in a Log Another way to gain insight into the performance of your script is to generate log records that contain time stamps. The clock seconds value is too coarse, but you can couple it with the clock clicks value to get higher resolution measurements. Use the code shown inExample 13-1 on page 185 to calibrate the clicks per second on your system. Example 13-13 writes log records that contain the current time and the number of clicks since the last record. There will be occasional glitches in the clicks value when the system counter wraps around or is reset by the system clock, but it will normally give pretty accurate results. The Log procedure adds overhead, too, so you should take several measurements in a tight loop to see how long each Log call takes: Example 13-13 Time Stamps in log records proc Log {args} { global log if [info exists log(file)] { set now [clock clicks] puts $log(file) [format "%s (%d)\t%s" \ [clock format [clock seconds]] \ [expr $now - $log(last)] \ [join $args " "]] set log(last) $now } } proc Log_Open {file} { . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks global log catch {close $log(file)} set log(file) [open $file w] set log(last) [clock clicks] } proc Log_Flush {} { global log catch {flush $log(file)} } proc Log_Close {} { global log catch {close $log(file)} catch {unset log(file)} } A more advanced profile command is part of the Extended Tcl (TclX) package. The TclXprofile command monitors the number of calls, the CPU time, and the elapsed time spent in different procedures. The Tcl Compiler The built-in Tcl compiler improves performance in the following ways: Tcl scripts are converted into an internal byte-code format that is efficient to process. The byte codes are saved so that cost of compiling is paid only the first time you execute a procedure or loop. After that, execution proceeds much faster. Compilation is done as needed, so unused code is never compiled. If you redefine a procedure, it is recompiled the next time it is executed. Variables and command arguments are kept in a native format as long as possible and converted to strings only when necessary. There are several native types, including integers, floating point numbers, Tcl lists, byte codes, and arrays. There are C APIs for implementing new types. Tcl is still dynamically typed, so a variable can contain different types during its lifetime. Expressions and control structures are compiled into special byte codes, so they are executed more efficiently. Because expr does its own round of substitutions, the compiler generates better code if you group expressions with braces. This means that expressions go through only one round of substitutions. The compiler can generate efficient code because it does not have to worry about strange code like: set subexpr {$x+$y} expr 5 * $subexpr The previous expression is not fully defined until runtime, so it has to be parsed and executed each time it is used. If the expression is grouped with braces, then the compiler knows in advance what operations will be used and can generate byte codes to implement the expression more efficiently. The operation of the compiler is essentially transparent to scripts, but there are some differences in lists and expressions. These are described in Chapter 54. With lists, the good news is that large lists are more efficient. The problem is that lists are parsed more aggressively, so syntax errors at the end of a list will be detected even if you access only the beginning of the list. There were also some bugs in the code generator in the widely used Tcl 8.0p2 release. Most of these were corner cases like unbraced expressions in if and while commands. Most of these bugs were fixed in the 8.0.3 patch release, and the rest were cleaned up in Tcl 8.1 with the addition of a new internal parsing package. The internal compiler continues to improve over time, with 8.4 extending the core instruction table to significantly improve performance over previous versions. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 14. Namespaces Namespaces group procedures and variables into separate name spaces. Namespaces were added in Tcl 8.0. This chapter describes the namespace and variable commands. Namespaces provide new scopes for procedures and global variables. Originally Tcl had one global scope for shared variables, local scopes within procedures, and one global namespace for procedures. The single global scope for procedures and global variables can become unmanageable as your Tcl application grows. I describe some simple naming conventions on page 181 that I have used successfully in large programs. The namespace facility is a more elegant solution that partitions the global scope for procedure names and global variables. Namespaces help structure large Tcl applications, but they add complexity. In particular, command callbacks may have to be handled specially so that they execute in the proper namespace. You choose whether or not you need the extra structure and learning curve of namespaces. If your applications are small, then you can ignore the namespace facility. If you are developing library packages that others will use, you should pick a namespace for your procedures and data so that they will not conflict with the applications in which they are used. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Using Namespaces Namespaces add new syntax to procedure and variable names. A double colon, ::, separates the namespace name from the variable or procedure name. You use this syntax to reference procedures and variables in a different namespace. The namespace import command lets you name things in other namespaces without the extra syntax. Namespaces can be nested, so you can create a hierarchy of scopes. These concepts are explained in more detail in the rest of this chapter. One feature not provided by namespaces is any sort of protection, or a way to enforce access controls between different namespaces. This sort of thing is awkward, if not impossible, to provide in a dynamic language like Tcl. For example, you are always free to use namespace eval to reach into any other namespace. Instead of providing strict controls, namespaces are meant to provide structure that enables large scale programming. The package facility described in Chapter 12 was designed before namespaces. This chapter illustrates a style that ties the two facilities together, but they are not strictly related. It is possible to create a package named A that implements a namespaceB, or to use a package without namespaces, or a namespace without a package. However, it makes sense to use the facilities together. Example 14-1 repeats the random number generator from Example 7-4 on page 91 using namespaces. The standard naming style conventions for namespaces use lowercase: Example 14-1 Random number generator using namespaces package provide random 1.0 namespace eval random { # Create a variable inside the namespace variable seed [clock seconds] # Make the procedures visible to namespace import namespace export init random range # Create procedures inside the namespace proc init { value } { variable seed set seed $value } proc random {} { variable seed set seed [expr {($seed*9301 + 49297) % 233280}] return [expr {$seed/double(233280)}] } proc range { range } { expr {int([random]*$range)} } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } Example 14-1 defines three procedures and a variable inside the namespacerandom. From inside the namespace, you can use these procedures and variables directly. From outside the namespace, you use the :: syntax for namespace qualifiers. For example, the state variable is just seed within the namespace, but you userandom::seed to refer to the variable from outside the namespace. Using the procedures looks like this: random::random => 0.3993355624142661 random::range 10 => 4 If you use a package a lot you can import its procedures. A namespace declares what procedures can be imported with thenamespace export command. Once you import a procedure, you can use it without a qualified name: namespace import random::random random => 0.54342849794238679 Importing and exporting are described in more detail later. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Namespace Variables The variable command defines a variable inside a namespace. It is like theset command because it can define a value for the variable. You can declare several namespace variables with one variable command. The general form is: variable name ?value? ?name value? ... If you have an array, do not assign a value in the variable command. Instead, use regular Tcl commands after you declare the variable. You can put any commands inside a namespace block: namespace eval foo { variable arr array set arr {name value name2 value2} } A namespace variable is similar to a global variable because it is outside the scope of any procedures. Procedures use the variable command or qualified names to reference namespace variables. For example, the random procedure has a variable command that brings the namespace variable into the current scope: variable seed If a procedure has a variable command that names a new variable, it is created in the namespace when it is firstset. Watch out for conflicts with global variables. You need to be careful when you use variables inside a namespace block. If you declare them with a variable command, they are clearly namespace variables. However, if you forget to declare them, then they will either become namespace variables, or latch onto an existing global variable by the same name. Consider the following code: namespace eval foo { variable table for {set i 1} {$i <= 256} {incr i} { set table($i) [format %c $i] } } If there is already a global variable i, then the for loop will use that variable. Otherwise, it will create thefoo::i variable. I found this behavior surprising, but it does make it easier to access global variables like env without first declaring them withglobal inside the namespace block. Qualified Names This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A fully qualified name begins with ::, which is the name for the global namespace. A fully qualified name unambiguously names a procedure or a variable. The fully qualified name works anywhere. If you use a fully qualified variable name, it is not necessary to use a global command. For example, suppose namespace foo has a namespace variable x, and there is also a global variablex. The global variable x can be named with this: ::x The :: syntax does not affect variable substitutions. You can get the value of the global variablex with $::x. Name the namespace variablex with this: ::foo::x A partially qualified name does not have a leading ::. In this case the name is resolved from the current namespace. For example, the following also names the namespace variable x: foo::x You can use qualified names with global. Once you do this, you can access the variable with its short name: global ::foo::x set x 5 Declaring variables is more efficient than using qualified names. The Tcl byte-code compiler generates faster code when you declare namespace and global variables. Each procedure context has its own table of variables. The table can be accessed by a direct slot index, or by a hash table lookup of the variable name. The hash table lookup is slower than the direct slot access. When you use the variable or global command, then the compiler can use a direct slot access. If you use qualified names, the compiler uses the more general hash table lookup. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Command Lookup A command is looked up first in the current name space. If it is not found there, then it is looked up in the global namespace. This means that you can use all the built-in Tcl commands inside a namespace with no special effort. You can play games by redefining commands within a namespace. For example, a namespace could define a procedure named set. To get the built-in set you could use ::set, while set referred to the set defined inside namespace. Obviously you need to be quite careful when you do this. You can use qualified names when defining procedures. This eliminates the need to put the proc commands inside a namespace block. However, you still need to use namespace eval to create the namespace before you can create procedures inside it.Example 14-2 repeats the random number generator using qualified names. random::init does not need a variable command because it uses a qualified name for seed: Example 14-2 Random number generator using qualified names namespace eval random { # Create a variable inside the namespace variable seed [clock seconds] } # Create procedures inside the namespace proc random::init { seed } { set ::random::seed $seed } proc random::random {} { variable seed set seed [expr {($seed*9301 + 49297) % 233280}] return [expr {$seed/double(233280)}] } proc random::range { range } { expr {int([random]*$range)} } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Nested Namespaces Namespaces can be nested inside other namespaces. Example 14-3 shows three namespaces that have their own specific variablex. The fully qualified names for these variables are ::foo::x, ::bar::x, and ::bar::foo::x. Example 14-3 Nested namespaces namespace eval foo { variable x 1 ;# ::foo::x } namespace eval bar { variable x 2 ;# ::bar::x namespace eval foo { variable x 3 ;# ::bar::foo::x } puts $foo::x ;# prints 3 } puts $foo::x ;# prints 1 Partially qualified names can refer to two different objects. In Example 14-3 the partially qualified name foo::x can reference one of two variables depending on the current namespace. From the global scope the name foo::x refers to the namespace variable x inside ::foo. From the ::bar namespace, foo::x refers to the variable x inside ::bar::foo. If you want to unambiguously name a variable in the current namespace, you have two choices. The simplest is to bring the variable into scope with the variable command: variable x set x something If you need to give out the name of the variable, then you have two choices. The most general solution is to use the namespace current command to create a fully qualified name: trace variable [namespace current]::x r \ [namespace current]::traceproc However, it is simpler to just explicitly write out the namespace as in: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks trace variable ::myname::x r ::myname::traceproc The drawback of this approach is that it litters your code with references to ::myname::, which might be subject to change during program development. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Importing and Exporting Procedures Commands can be imported from namespaces to make it easier to name them. An imported command can be used without its namespace qualifier. Each namespace specifies exported procedures that can be the target of an import. Variables cannot be imported. Note that importing is only a convenience; you can always use qualified names to access any procedure. As a matter of style, I avoid importing names, so I know what package a command belongs to when I'm reading code. The namespace export command goes inside the namespace block, and it specifies what procedures a namespace exports. The specification is a list of string match patterns that are compared against the set of commands defined in a namespace. The export list can be defined before the procedures being exported. You can do more than one namespace export to add more procedures, or patterns, to the export list for a namespace. Use the -clear flag if you need to reset the export list. namespace export ?-clear? ?pat? ?pat? ... Only exported names appear in package indexes. When you create the pkgIndex.tcl package index file with pkg_mkIndex, which is described Chapter 12, you should be aware that only exported names appear in the index. Because of this, I often resort to exporting everything. I never plan to import the names, but I do rely on automatic code loading based on the index files. This exports everything: namespace export * The namespace import command makes commands in another namespace visible in the current namespace. Animport can cause conflicts with commands in the current namespace. The namespace import command raises an error if there is a conflict. You can override this with the -force option. The general form of the command is: namespace import ?-force? namespace::pat ?namespace::pat?... The pat is a string match type pattern that is matched against exported commands defined in namespace. You cannot use patterns to match namespace. The namespace can be a fully or partially qualified name of a namespace. If you are lazy, you can import all procedures from a namespace: namespace import random::* The drawback of this approach is that random exports an init procedure, which might conflict with another module you import in the same way. It is safer to import just the procedures you plan on using: namespace import random::random random::range This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A namespace import takes a snapshot. If the set of procedures in a namespace changes, or if its export list changes, then this has no effect on any imports that have already occurred from that namespace. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Callbacks and Namespaces Commands like after, bind, and button take arguments that are Tcl scripts that are evaluated later. Thesecallback commands execute later in the global scope by default. If you want a callback to be evaluated in a particular namespace, you can construct the callback with namespace code. This command does not execute the callback. Instead, it generates a Tcl command that will execute in the current namespace scope when it is evaluated later. For example, suppose ::current is the current namespace. The namespace code command determines the current scope and adds that to the namespace inscope command it generates: set callback [namespace code {set x 1}] => namespace inscope ::current {set x 1} # sometime later ... eval $callback When you evaluate $callback later, it executes in the ::current namespace because of the namespace inscope command. In particular, if there is a namespace variable ::current::x, then that variable is modified. An alternative to using namespace code is to name the variable with a qualified name: set callback {set ::current::x 1} The drawback of this approach is that it makes it tedious to move the code to a different namespace. If you need substitutions to occur on the command when you define it, use list to construct it. Using list is discussed in more detail on pages 131 and 455. Example 14-4 wraps up the list and the namespace inscope into the code procedure, which is handy because you almost always want to use list when constructing callbacks. The uplevel in code ensures that the correct namespace is captured; you can usecode anywhere: Example 14-4 The code procedure to wrap callbacks proc code {args} { set namespace [uplevel {namespace current}] return [list namespace inscope $namespace $args] } namespace eval foo { variable y "y value" x {} set callback [code set x $y] => namespace inscope ::foo {set x {y value}} } The example defines a callback that will set ::foo::x to y value. If you want to set x to the value that y has at the time of the callback, then you do not want to do any substitutions. In that case, the original namespace code is what you want: set callback [namespace code {set x $y}] => namespace inscope ::foo {set x $y} This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks If the callback has additional arguments added by the caller, namespace inscope correctly adds them. For example,the scrollbar protocol described on page 501 adds parameters to the callback that controls a scrollbar. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than [ Team LiB ] Introspection The info commands operation returns all the commands that are currently visible. It is described in more detail on page 190. You can limit the information returned with a string match pattern. You can also include a namespace specifier in the pattern to see what is visible in a namespace. Remember that global commands and imported commands are visible, so info commands returns more than just what is defined by the namespace. Example 14-5 uses namespace origin, which returns the original name of imported commands, to sort out the commands that are really defined in a namespace: Example 14-5 Listing commands defined by a namespace proc Namespace_List {{namespace {}}} { if {[string length $namespace] == 0} { # Determine the namespace of our caller set namespace [uplevel {namespace current}] } set result {} foreach cmd [info commands ${namespace}::*] { if {[namespace origin $cmd] == $cmd} { lappend result $cmd } } return [lsort $result] } [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The namespace Command Table 14-1 summarizes the namespace operations: Table 14-1. The namespace command namespace current Returns the current namespace. namespace children ?name? Returns names of nested namespaces. name defaults to current namespace. pat is a string match ?pat? pattern that limits what is returned. namespace code script Generates a namespace inscope command that will eval script in the current namespace. namespace delete name ?name? Deletes the variables and commands from the specified namespaces. ... namespace eval name cmd ?args? Concatenates args, if present, onto cmd and evaluates it in name namespace. ... namespace exists name Returns 1 if namespace name exists, 0 otherwise. (Tcl 8.4) namespace export ?-clear? ?pat? Adds patterns to the export list for current namespace. Returns export list if no patterns. ?pat? ... namespace forget pat ?pat? ... Undoes the import of names matching patterns. namespace import ?-force? pat Adds the names matching the patterns to the current namespace. ?pat? ... namespace inscope name cmd Appends args, if present, onto cmd as list elements and evaluates it inname namespace. ?args? ... namespace origin cmd Returns the original name of cmd. namespace parent ?name? Returns the parent namespace of name, or of the current namespace. namespace qualifiers name Returns the part of name up to the last :: in it. namespace which ?flag? name Returns the fully qualified version of name. The flag is one of -command, -variable, or -namespace. namespace tail name Returns the last component of name. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Converting Existing Packages to use Namespaces Suppose you have an existing set of Tcl procedures that you want to wrap in a namespace. Obviously, you start by surrounding your existing code in a namespace eval block. However, you need to consider three things: global variables, exported procedures, and callbacks. Global variables remain global until you change your code to use variable instead of global. Some variables may make sense to leave at the global scope. Remember that the variables that Tcl defines are global, including env, tcl_platform, and the others listed in Table 2-2 on page 31. If you use the upvar #0 trick described on page 92, you can adapt this to namespaces by doing this instead: upvar #0 [namespace current]::$instance state Exporting procedures makes it more convenient for users of your package. It is not strictly necessary because they can always use qualified names to reference your procedures. An export list is a good hint about which procedures are expected to be used by other packages. Remember that the export list determines what procedures are visible in the index created by pkg_mkIndex. Callbacks execute at the global scope. If you use variable traces and variables associated with Tk widgets, these are also treated as global variables. If you want a callback to invoke a namespace procedure, or if you give out the name of a namespace variable, then you must construct fully qualified variable and procedure names. You can hardwire the current namespace: button .foo -command ::myname::callback \ -textvariable ::myname::textvar or you can use namespace current: button .foo -command [namespace current]::callback \ -textvariable [namespace current]::textvar [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] [incr Tcl] Object System The Tcl namespace facility does not provide classes and inheritance. It just provides new scopes and a way to hide procedures and variables inside a scope. There are Tcl C APIs that support hooks in variable name and command lookup for object systems so that they can implement classes and inheritance. By exploiting these interfaces, various object systems can be added to Tcl as shared libraries. The Tcl namespace facility was proposed by Michael McLennan based on his experiences with [incr Tcl], which is the most widely used object-oriented extension for Tcl. [incr Tcl] provides classes, inheritance, and protected variables and commands. If you are familiar with C++, [incr Tcl] should feel similar. A complete treatment of [incr Tcl] is not made in this book. [incr Tcl] From The Ground Up (Chad Smith, Osborn-McGraw Hill, 1999) is an excellent source of information. You can find a version of [incr Tcl] on the CD-ROM. The [incr Tcl] home page is: http://www.tcltk.com/itcl/ The [incr Tcl] sources are maintained on SourceForge: http://incrtcl.sourceforge.net/ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] xotcl Object System Xotcl is a more recently developed object-oriented extension that blends object-orientation and scripting in a way that preserves the benefits of both. It includes features such as dynamic object aggregation, per-object mixins, filters, dynamic component loading and more. The xotcl home page is: http://www.xotcl.org/ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Notes The final section of this chapter touches on a variety of features of the namespace facility. Names for Widgets, Images, and Interpreters There are a number of Tcl extensions that are not affected by the namespaces described in this chapter, which apply only to commands and variable names. For example, when you create a Tk widget, a Tcl command is also created that corresponds to the Tk widget. This command is always created in the global command namespace even when you create the Tk widget from inside a namespace eval block. Other examples include Tcl interpreters, which are described in Chapter 19, and Tk images, which are described inChapter 41. The variable command at the global scope It turns out that you can use variable like the global command if your procedures are not inside a namespace. This is consistent because it means "this variable belongs to the current namespace," which might be the global namespace. Auto Loading and auto_import The following sequence of commands can be used to import commands from the foo package: package require foo namespace import foo::* However, because of the default behavior of packages, there may not be anything that matches foo::* after the package require. Instead, there are entries in the auto_index array that will be used to load those procedures when you first use them. The auto loading mechanism is described in Chapter 12. To account for this, Tcl calls out to a hook procedure calledauto_import. This default implementation of this procedure searches auto_index and forcibly loads any pending procedures that match the import pattern. Packages like [incr Tcl] exploit this hook to implement more elaborate schemes. The auto_import hook was first introduced in Tcl 8.0.3. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Namespaces and uplevel Namespaces affect the Tcl call frames just like procedures do. If you walk the call stack with info level, the namespace frames are visible. This means that you can get access to all variables with uplevel and upvar. Level #0 is still the absolute global scope, outside any namespace or procedure. Try out Call_Trace from Example 13-5 on page 190 on your code that uses namespaces to see the effect. Naming Quirks When you name a namespace, you are allowed to have extra colons at the end. You can also have two or more colons as the separator between namespace name components. These rules make it easier to assemble names by adding to the value returned from namespace current. These all name the same namespace: ::foo::bar ::foo::bar:: ::foo:::::::bar The name of the global namespace can be either :: or the empty string. This follows from the treatment of:: in namespace names. When you name a variable or command, a trailing :: is significant. In the following command a variable inside the::foo::bar namespace is modified. The variable has an empty string for its name! set ::foo::bar:: 3 namespace eval ::foo::bar { set {} } => 3 If you want to embed a reference to a variable just before two colons, use a backslash to turn off the variable name parsing before the colons: set x xval set y $x\::foo => xval::foo Miscellaneous You can remove names you have imported: namespace forget random::init You can rename imported procedures to modify their names: rename range Range This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks You can even move a procedure into another namespace with rename: rename random::init myspace::init [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 15. Internationalization This chapter describes features that support text processing for different character sets such as ASCII and Japanese. Tcl can read and write data in various character set encodings, but it processes data in a standard character set called Unicode. Tcl has a message catalog that lets you generate different versions of an application for different languages. Tcl commands described are: encoding and msgcat. Different languages use different alphabets, or character sets. An encoding is a standard way to represent a character set. Tcl hides most of the issues associated with encodings and character sets, but you need to be aware of them when you write applications that are used in different countries. You can also write an application using a message catalog so that the strings you display to users can be in the language of their choice. Using a message catalog is more work, but Tcl makes it as easy as possible. Most of the hard work in dealing with character set encodings is done "under the covers" by the Tcl C library. The Tcl C library underwent substantial changes to support international character sets. Instead of using 8-bit bytes to store characters, Tcl uses a 16-bit character set called Unicode, which is large enough to encode the alphabets of all languages. There is also plenty of room left over to represent special characters like and . In spite of all the changes to support Unicode, there are few changes visible to the Tcl script writer. Scripts written for Tcl 8.0 and earlier continue to work fine with Tcl 8.1 and later versions. You only need to modify scripts if you want to take advantage of the features added to support internationalization. This chapter begins with a discussion of what a character set is and why different codings are used to represent them. It concludes with a discussion of message catalogs. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Character Sets and Encodings If you are from the United States, you've probably never thought twice about character sets. Most computers use the ASCII encoding, which has 127 characters. That is enough for the 26 letters in the English alphabet, upper case and lower case, plus numbers, various punctuation characters, and control characters like tab and newline. ASCII fits easily in 8-bit characters, which can represent 256 different values. European alphabets include accented characters like è, ñ, and ä. The ISO Latin-1 encoding is a superset of ASCII that encodes 256 characters. It shares the ASCII encoding in values 0 through 127 and uses the "high half" of the encoding space to represent accented characters as well as special characters like ©. There are several ISO Latin encodings to handle different alphabets, and these share the trick of encoding ASCII in the lower half and other characters in the high half. You might see these encodings referred to as iso8859-1, iso8859-2, and so on. Asian character sets are simply too large to fit into 8-bit encodings. There are a number of 16-bit encodings for these languages. If you work with these, you are probably familiar with the "Big 5" or ShiftJIS encodings. Unicode is an international standard character set encoding. There are both 16-bit Unicode and 32-bit Unicode standards, but Tcl and just about everyone else use the 16-bit standard. Unicode has the important property that it can encode all the important character sets without conflicts and overlap. By converting all characters to the Unicode encoding, Tcl can work with different character sets simultaneously. As of 8.4, Tcl is compliant with Unicode v3.1. For more information on Unicode, see http://www.unicode.org/ The System Encoding Computer systems are set up with a standard system encoding for their files. If you always work with this encoding, then you can ignore character set issues. Tcl will read files and automatically convert them from the system encoding to Unicode. When Tcl writes files, it automatically converts from Unicode to the system encoding. If you are curious, you can find out the system encoding with: encoding system => cp1252 The "cp" is short for "code page," the term that Windows uses to refer to different encodings. On my Unix system, the system encoding is iso8859-1. Do not change the system encoding. You could also change the system encoding with: encoding system encoding This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks But this is not a good idea. It immediately changes how Tcl passes strings to your operating system, and it is likely to leave Tcl in an unusable state. Tcl automatically determines the system encoding for you. Don't bother trying to set it yourself. The encoding names command lists all the encodings that Tcl knows about. The encodings are kept in files stored in the encoding directory under the Tcl script library. They are loaded automatically the first time you use an encoding. [View full width] lsort [encoding names] => ascii big5 cp1250 cp1251 cp1252 cp1253 cp1254 cp1255 cp1256 cp1257 cp1258 cp437 cp737 cp775 cp850 cp852 cp855 cp857 cp860 cp861 cp862 cp863 cp864 cp865 cp866 cp869 cp874 cp932 cp936 cp949 cp950 dingbats euc-cn euc-jp euc-kr gb12345 gb1988 gb2312 identity iso2022 iso2022-jp iso2022-kr iso8859-1 iso8859-2 iso8859-3 iso8859-4 iso8859-5 iso8859-6 iso8859-7 iso8859-8 iso8859-9 jis0201 jis0208 jis0212 ksc5601 macCentEuro macCroatian macCyrillic macDingbats macGreek macIceland macJapan macRoman macRomania macThai macTurkish macUkraine shiftjis symbol unicode utf-8 The encoding names reflect their origin. The "cp" refers to the "code pages" that Windows uses to manage encodings. The "mac" encodings come from the Macintosh. The "iso," "euc," "gb," and "jis" encodings come from various standards bodies. File Encodings and fconfigure The conversion to Unicode happens automatically in the Tcl C library. When Tcl reads and writes files, it translates from the current system encoding into Unicode. If you have files in different encodings, you can use the fconfigure command to set the encoding. For example, to read a file in the standard Russian encoding (iso8859-7): set in [open README.russian] fconfigure $in -encoding iso8859-7 [*] Example 15-1 shows a simple utility I use inexmh, a MIME-aware mail reader. MIME has its own convention for specifying the character set encoding of a mail message that differs slightly from Tcl's naming convention. The procedure launders the name and then sets the encoding. Exmh was already aware of MIME character sets, so it could choose fonts for message display. Adding this procedure and adding two calls to it was all I had to do to adapt exmh to Unicode. [*] The exmh home page is http://www.beedub.com/exmh/. It is a wonderful tool that helps me manage tons of email. It is written in Tcl/Tk, of course, and relies on the MH mail system, which limits it to UNIX. Example 15-1 MIME character sets and file encodings proc Mime_SetEncoding {file charset} { regsub -all {(iso|jis|us)-} $charset {\1} charset set charset [string tolower charset] regsub usascii $charset ascii charset fconfigure $file -encoding $charset } . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Scripts in Different Encodings If you have scripts that are not in the system encoding, then you cannot use source to load them. However, it is easy to read the files yourself under the proper encoding and use eval to process them. Example 15-2 adds a -encoding flag to the source command. This is likely to become a built-in feature in future versions of Tcl so that commands like info script will work properly: Example 15-2 Using scripts in nonstandard encodings proc Source {args} { set file [lindex $args end] if {[llength $args] == 3 && [string equal -encoding [lindex $args 0]]} { set encoding [lindex $args 1] set in [open $file] fconfigure $in -encoding $encoding set script [read $in] close $in return [uplevel 1 $script] } elseif {[llength $args] == 1} { return [uplevel 1 [list source $file]] } else { return -code error \ "Usage: Source ?-encoding encoding? file?" } } Unicode and UTF-8 UTF-8 is an encoding for Unicode. While Unicode represents all characters with 16 bits, the UTF-8 encoding uses either 8, 16, or 24 bits to represent one Unicode character. This variable-width encoding is useful because it uses 8 bits to represent ASCII characters. This means that a pure ASCII string, one with character codes all less than 128, is also a UTF-8 string. Tcl uses UTF-8 internally to make the transition to Unicode easier. It allows interoperability with Tcl extensions that have not been made Unicode-aware. They can continue to pass ASCII strings to Tcl, and Tcl will interpret them correctly. As a Tcl script writer, you can mostly ignore UTF-8 and just think of Tcl as being built on Unicode (i.e., full 16-bit character set support). If you write Tcl extensions in C or C++, however, the impact of UTF-8 and Unicode is quite visible. This is explained in more detail in Chapter 47. Tcl lets you read and write files in UTF-8 encoding or directly in Unicode. This is useful if you need to use the same file on systems that have different system encodings. These files might be scripts, message catalogs, or documentation. Instead of using a particular native format, you can use Unicode or UTF-8 and read the files the same way on any of your systems. Of course, you will have to set the encoding properly by using fconfigure as shown earlier. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The Binary Encoding If you want to read a data file and suppress all character set transformations, use the binary encoding: fconfigure $in -encoding binary Under the binary encoding, Tcl reads in each 8-bit byte and stores it into the lower half of a 16-bit Unicode character with the high half set to zero. During binary output, Tcl writes out the lower byte of each Unicode character. You can see that reading in binary and then writing it out doesn't change any bits. Watch out if you read something in one encoding and then write it out in binary. Any information in the high byte of the Unicode character gets lost! Tcl actually handles the binary encoding more efficiently than just described, but logically the previous description is still accurate. As described in Chapter 47, Tcl can manage data in several forms, not just strings. When you read a file in binary format, Tcl stores the data as a ByteArray that is simply 8 bits of data in each byte. However, if you ask for this data as a string (e.g., with the puts command), Tcl automatically converts from 8-bit bytes to 16-bit Unicode characters by setting the high byte to all zeros. The binary command also manipulates data inByteArray format. If you read a file with the binary encoding and then use thebinary command to process the data, Tcl will keep the data in an efficient form. The string command also understands the ByteArray format, so you can do operations likestring length, string range, and string index on binary data without suffering the conversion cost from a ByteArray to a UTF-8 string. Conversions Between Encodings The encoding command lets you convert strings between encodings. Theencoding convertfrom command converts data in some other encoding into a Unicode string. The encoding convertto command converts a Unicode string into some other encoding. For example, the following two sequences of commands are equivalent. They both read data from a file that is in Big5 encoding and convert it to Unicode: fconfigure $input -encoding gb12345 set unicode [read $input] or fconfigure $input -encoding binary set unicode [encoding convertfrom gb12345 [read $input]] In general, you can lose information when you go from Unicode to any other encoding, so you ought to be aware of the limitations of the encodings you are using. In particular, the binary encoding may not preserve your data if it starts out from an arbitrary Unicode string. Similarly, an encoding like iso8859-2 may simply not have a representation of a given Unicode character. The encoding Command This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 15-1 summarizes the encoding command: Table 15-1. The encoding command encoding convertfrom Converts binary data from the specified encoding, which defaults to the system encoding, into ?encoding?data Unicode. encoding convertto ?encoding? Converts string from Unicode into data in theencoding format, which defaults to the system string encoding. encoding names Returns the names of known encodings. encoding system ?encoding? Queries or change the system encoding. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Message Catalogs A message catalog is a list of messages that your application will display. The main idea is that you can maintain several catalogs, one for each language you support. Unfortunately, you have to be explicit about using message catalogs. Everywhere you generate output or display strings in Tk widgets, you need to change your code to go through a message catalog. Fortunately, Tcl uses a nice trick to make this fairly easy and to keep your code readable. Instead of using keys like "message42" to get messages out of the catalog, Tcl just uses the strings you would use by default. For example, instead of this code: puts "Hello, World!" A version that uses message catalogs looks like this: puts [msgcat::mc "Hello, World!"] If you have not already loaded your message catalog, or if your catalog doesn't contain a mapping for "Hello, World!", then msgcat::mc just returns its argument. Actually, you can define just what happens in the case of unknown inputs by defining your own msgcat::mcunknown procedure, but the default behavior is quite good. The message catalog is implemented in Tcl in the msgcat package. You need to use package require to make it available to your scripts: package require msgcat In addition, all the procedures in the package begin with "mc," so you can use namespace import to shorten their names further. I am not a big fan of namespace import, but if you use message catalogs, you will be calling themsgcat::mc function a lot, so it may be worthwhile to import it: namespace import msgcat::mc puts [mc "Hello, World!"] Specifying a Locale A locale identifies a language or language dialect to use in your output. A three-level scheme is used in the locale identifier: language_country_dialect The language codes are defined by the ISO-3166 standard. For example, "en" is English and "es" is Spanish. The country codes are defined by the ISO-639 standard. For example, US is for the United States and UK is for the United Kingdom. The dialect is up to you. The country and dialect parts are optional. Finally, the locale specifier is case insensitive. The following examples are all valid locale specifiers: es en en_US This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks en_us en_UK en_UK_Scottish en_uk_scottish Users can set their initial locale with the LANG and LOCALE environment variables. If there is no locale information in the environment, then the "c" locale is used (i.e., the C programming language.) You can also set and query the locale with the msgcat::mclocale procedure: msgcat::mclocale => c msgcat::mclocale en_US The msgcat::mcpreferences procedure returns a list of the user's locale preferences from most specific (i.e., including the dialect) to most general (i.e., only the language). For example: msgcat::mclocale en_UK_Scottish msgcat::mcpreferences => en_UK_Scottish en_UK en Managing Message Catalog Files A message catalog is simply a Tcl source file that contains a series of msgcat::mcset commands that define entries in the catalog. The syntax of the msgcat::mcset procedure is: msgcat::mcset locale src-string ?dest-string? The locale is a locale description like es or en_US_Scottish. The src-string is the string used as the key when callingmsgcat::mc. The dest-string is the result of msgcat::mc when the locale is in force. The msgcat::mcload procedure should be used to load your message catalog files. It expects the files to be named according to their locale (e.g., en_US_Scottish.msg), and it binds the message catalog to the current namespace. The msgcat::mcload procedure loads files that match themsgcat::mcpreferences and have the .msg suffix. For example, with a locale of en_UK_Scottish, msgcat::mcload would look for these files: en_UK_Scottish.msg en_UK.msg en.msg The standard place for message catalog files is in the msgs directory below the directory containing a package. With this arrangement you can call msgcat::mcload as shown below. The use of info script to find related files is explained on page 192. msgcat::mcload [file join [file dirname [info script]] msgs] The message catalog file is sourced, so it can contain any Tcl commands. You might find it convenient to import the msgcat::mcset procedure. Be sure to use -force with namespace import because that command might already have been imported as a result of loading other message catalog files. Example 15-3 shows three trivial message catalog files: Example 15-3 Three sample message catalog files This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks ## en.msg namespace import -force msgcat::mcset mcset en Hello Hello_en mcset en Goodbye Goodbye_en mcset en String String_en # end of en.msg ## en_US.msg namespace import -force msgcat::mcset mcset en_US Hello Hello_en_US mcset en_US Goodbye Goodbye_en_US # end of en_US.msg ## en_US_Texan.msg namespace import -force msgcat::mcset mcset en_US_Texan Hello Howdy! # end of en_US_Texan.msg Assuming the files from Example 15-3 are all in themsgs directory below your script, you can load all these files with these commands: msgcat::mclocale en_US_Texan msgcat::mcload [file join [file dirname [info script]] msgs] The dialect has the highest priority: msgcat::mc Hello => Howdy! If the dialect does not specify a mapping, then the country mapping is checked: msgcat::mc Goodbye => Goodbye_en_US Finally, the lowest priority is the language mapping: msgcat::mc String => String_en Message Catalogs and Namespaces What happens if two different library packages have conflicting message catalogs? Suppose the foo package contains this call: msgcat::set fr Hello Bonjour But the bar package contains this conflicting definition: msgcat::mcset fr Hello Ello This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks What happens is that msgcat::mcset and msgcat::mc are sensitive to the current Tcl namespace. Namespaces are described in detail in Chapter 14. If the foo package loads its message catalog while inside thefoo namespace, then any calls tomsgcat::mc from inside the foo namespace will see those definitions. In fact, if you call msgcat::mc from inside any namespace, it will find only message catalog definitions defined from within that namespace. If you want to share message catalogs between namespaces, you will need to implement your own version of msgcat::mcunknown that looks in the shared location. Example 15-4 shows a version that looks in the global namespace before returning the default string. Example 15-4 Using msgcat::mcunknown to share message catalogs proc msgcat::mcunknown {local src} { variable insideUnknown if {![info exist insideUnknown]} { # Try the global namespace, being careful to note # that we are already inside this procedure. set insideUnknown true set result [namespace eval :: [list \ msgcat::mc $src \ ]] unset insideUnknown return $result } else { # Being called because the message isn't found # in the global namespace return $src } } The msgcat package Table 15-2 summarizes the msgcat package. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 15-2. The msgcat package msgcat::mc src Returns the translation of src according to the current locale and namespace. msgcat::mclocale ?locale? Queries or set the current locale. msgcat::mcmax ?src-string src-string Returns the length of the longest src-string after translation. (Tcl 8.3) ...? msgcat::mcpreferences Returns a list of locale preferences ordered from the most specific to the most general. msgcat::mcload directory Loads message files for the current locale fromdirectory. msgcat::mcset locale src translation Defines a mapping for the src string in locale to the translation string. (Tcl 8.3) msgcat::mcmset src-trans-list Define multiple src-translation pairs in a single call. msgcat::mcunknown locale src This procedure is called to resolve unknown translations. Applications can provide their own implementations. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 16. Event-Driven Programming This chapter describes event-driven programming using timers and asynchronous I/O facilities. The after command causes Tcl commands to occur at a time in the future, and the fileevent command registers a command to occur in response to file input/output (I/O). Tcl commands discussed are: after, fblocked, fconfigure, fileevent, and vwait. Event-driven programming is used in long-running programs like network servers and graphical user interfaces. This chapter introduces event-driven programming in Tcl. Tcl provides an easy model in which you register Tcl commands, and the system then calls those commands when a particular event occurs. The after command is used to execute Tcl commands at a later time, and thefileevent command is used to execute Tcl commands when the system is ready for I/O. The vwait command is used to wait for events. During the wait, Tcl automatically calls Tcl commands that are associated with different events. The event model is also used when programming user interfaces using Tk. Originally, event processing was associated only with Tk. The event loop moved from Tk to Tcl in the Tcl 7.5/Tk 4.1 release. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The Tcl Event Loop An event loop is built into Tcl, which checks for events and calls out to handlers that have been registered for different types of events. Some of the events are processed internally to Tcl. You can register Tcl commands to be called in response to events. There are also C APIs for the event loop, which are described on page 781. Event processing is active all the time in Tk applications. If you do not use Tk, you can start the event loop with the vwait command as shown inExample 16-2 on page 230. The four event classes are handled in the following order: Window events. These include keystrokes and button clicks. Handlers are set up for these automatically by the Tk widgets, and you can register window event handlers with the bind command described in Chapter 29. File and socket I/O events. The fileevent command registers handlers for these events. Timer events. The after command registers commands to occur at specific times. Idle events. These events are processed when there is nothing else to do. The Tk widgets use idle events to display themselves. The after idle command registers a command to run at the next idle time. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The after Command The after command sets up commands to happen in the future. In its simplest form, it pauses the application for a specified time, in milliseconds. The example below waits for half a second: after 500 During this time, the application does not process events. You can use the vwait command as shown on page 230 to keep the Tcl event loop active during the waiting period. The after command can register a Tcl command to occur after a period of time, in milliseconds: after milliseconds cmd arg arg... The after command treats its arguments like eval; if you give it extra arguments, it concatenates them to form a single command. If your argument structure is important, use list to build the command. The following example always works, no matter what the value ofmyvariable is: after 500 [list puts $myvariable] The return value of after is an identifier for the registered command. You can cancel this command with theafter cancel operation. You specify either the identifier returned from after, or the command string. In the latter case, the event that matches the command string exactly is canceled. Table 16-1 summarizes the after command: Table 16-1. The after command after milliseconds Pauses for milliseconds. after ms arg ?arg...? Concatenates the args into a command and executes it afterms milliseconds. Immediately returns an ID. after cancel id Cancels the command registered underid. after cancel command Cancels the registered command. after idle command Runs command at the next idle moment. after info ?id? Returns a list of IDs for outstandingafter events, or the command associated withid. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The fileevent Command The fileevent command registers a procedure that is called when an I/O channel is ready for read or write events. For example, you can open a pipeline or network socket for reading, and then process the data from the pipeline or socket using a command registered with fileevent. The advantage of this approach is that your application can do other things, like update the user interface, while waiting for data from the pipeline or socket. Network servers use fileevent to manage connections to many clients. You can use fileevent on stdin and stdout, too. Using network sockets is described in Chapter 17. The command registered with fileevent uses the regular Tcl commands to read or write data on the I/O channel. For example, if the pipeline generates line-oriented output, you should use gets to read a line of input. If you try and read more data than is available, your application may block waiting for more input. For this reason, you should read one line in your fileevent handler, assuming the data is line-oriented. If you know the pipeline will generate data in fixed-sized blocks, then you can use the read command to read one block. The fconfigure command, which is described on page 232, can put a channel into nonblocking mode. This is not strictly necessary when using fileevent. The pros and cons of nonblocking I/O are discussed later. End of file makes a channel readable. You should check for end of file in your read handler because it will be called when end of file occurs. It is important to close the channel inside the handler because closing the channel automatically unregisters the handler. If you forget to close the channel, your read event handler will be called repeatedly. Example 16-1 shows a read event handler. A pipeline is opened for reading and its command executes in the background. TheReader command is invoked when data is available on the pipe. When end of file is detected a variable is set, which signals the application waiting with vwait. Otherwise, a single line of input is read and processed. Thevwait command is described on the next page. Example 24-1 on page 378 also uses fileevent to read from a pipeline. Example 16-1 A read event file handler proc Reader { pipe } { global done if {[eof $pipe]} { catch {close $pipe} set done 1 return This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } gets $pipe line # Process the line here... } set pipe [open "|some command"] fileevent $pipe readable [list Reader $pipe] vwait done There can be at most one read handler and one write handler for an I/O channel. If you register a handler and one is already registered, then the old registration is removed. If you call fileevent without a command argument, it returns the currently registered command, or it returns the empty string if there is none. If you register the empty string, it deletes the current file handler. Table 16-2 summarizes the fileevent command. Table 16-2. The fileevent command fileevent fileId readable ?command? Queries or registers command to be called when fileId is readable. fileevent fileId writable ?command? Queries or registers command to be called when fileId is writable. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The vwait Command The vwait command waits until a variable is modified. For example, you can set variablex at a future time, and then wait for that variable to be set with vwait. set x 0 after 500 {set x 1} vwait x Waiting with vwait causes Tcl to enter the event loop. Tcl will process events until the variablex is modified. The vwait command completes when some Tcl code runs in response to an event and modifies the variable. In this case the event is a timer event, and the Tcl code is simply: set x 1 In some cases vwait is used only to start the event loop.Example 16-2 sets up a file event handler forstdin that will read and execute commands. Once this is set up, vwait is used to enter the event loop and process commands until the input channel is closed. The process exits at that point, so the vwait variable Stdin(wait) is not used: Example 16-2 Using vwait to activate the event loop proc Stdin_Start {prompt} { global Stdin set Stdin(line) "" puts -nonewline $prompt flush stdout fileevent stdin readable [list StdinRead $prompt] vwait Stdin(wait) } proc StdinRead {prompt} { global Stdin if {[eof stdin]} { exit } append Stdin(line) [gets stdin] if {[info complete $Stdin(line)]} { catch {uplevel #0 $Stdin(line)} result puts $result puts -nonewline $prompt flush stdout set Stdin(line) {} } else { append Stdin(line) \n This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The fconfigure Command The fconfigure command sets and queries several properties of I/O channels. The default settings for channels are suitable for most cases. If you do event-driven I/O you may want to set your channel into nonblocking mode. If you handle binary data, you should turn off end of line and character set translations. You can query the channel parameters like this: [View full width] fconfigure stdin => -blocking 1 -buffering none -buffersize 4096 -encoding iso8859-1 -eofchar {} -translation lf Table 16-3 summarizes the properties controlled byfconfigure, not including properties for serial lines. Table 16-3. I/O channel properties controlled by fconfigure -blocking Blocks until I/O channel is ready: 0 or 1. -buffering Buffer mode: none, line, or full. -buffersize Number of characters in the buffer. -encoding The character set encoding. -eofchar Special end of file character. Control-z (\x1a) for DOS. Null otherwise. -lasterror Returns the last POSIX error message associated with a channel. -translation End of line translation: auto, lf, cr, crlf, binary. -peername Sockets only. IP address of remote host. -peerport Sockets only. Port number of remote host. Serial lines have many additional properties. Before Tcl 8.4, you could only control the baud rate, parity and number of bits using the -mode property. Many new properties for serial line control were added in Tcl 8.4. Table 16-4 lists the serial line properties set byfconfigure. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 16-4. Serial line properties controlled by fconfigure -mode Format: baud,parity,data,stop. -queue Returns a list of two integers representing the current number of bytes in the input and output queues. Tcl 8.4. -timeout Specifies the timeout in milliseconds for blocking reads. Tcl 8.4. -ttycontrol Sets up the handshake output lines. Tcl 8.4. -ttystatus Returns the current serial line status. Tcl 8.4. -xchar Specifies the software handshake characters. Tcl 8.4. -handshake Specifies one of rtscts, xonxoff or (Windows only) dtrdsr. Tcl 8.4. -pollinterval Sets the maximum time for polling of fileevents (Windows only.) Tcl 8.4. -sysbuffer Specifies the size of system buffers for a serial channel. (Windows only.) Tcl 8.4. Nonblocking I/O By default, I/O channels are blocking. A gets or read will wait until data is available before returning. Aputs may also wait if the I/O channel is not ready to accept data. This behavior is all right if you are using disk files, which are essentially always ready. If you use pipelines or network sockets, however, the blocking behavior can hang up your application. The fconfigure command can set a channel into nonblocking mode. A gets or read command may return immediately with no data. This occurs when there is no data available on a socket or pipeline. A puts to a nonblocking channel will accept all the data and buffer it internally. When the underlying device (i.e., a pipeline or socket) is ready, then Tcl automatically writes out the buffered data. Nonblocking channels are useful because your application can do something else while waiting for the I/O channel. You can also manage several nonblocking I/O channels at once. Nonblocking channels should be used with the fileevent command described earlier. The following command puts a channel into nonblocking mode: fconfigure fileID -blocking 0 It is not strictly necessary to put a channel into nonblocking mode if you use fileevent. However, if the channel is in blocking mode, then it is still possible for the gets or read done by your fileevent procedure to block. For example, an I/O channel might have some data ready, but not a complete line. In this case, a gets would block, unless the channel is nonblocking. Perhaps the best motivation for a nonblocking channel is the buffering behavior of a nonblocking puts. You can even close a channel that has buffered data, and Tcl will automatically write out the buffers as the channel becomes ready. For these reasons, it is common to use a nonblocking channel with fileevent. Example 16-3 shows a fileevent handler for a nonblocking channel. As described above, thegets may not find a complete line, in which case it doesn't read anything and returns -1. Example 16-3 A read event file handler for a nonblocking channel This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set pipe [open "|some command"] fileevent $pipe readable [list Reader $pipe] fconfigure $pipe -blocking 0 proc Reader { pipe } { global done if {[eof $pipe]} { catch {close $pipe} set done 1 return } if {[gets $pipe line] < 0} { # We blocked anyway because only part of a line # was available for input } else { # Process one line } } vwait done The fblocked Command The fblocked command returns 1 if a channel does not have data ready. Normally the fileevent command takes care of waiting for data, so I have seen fblocked useful only in testing channel implementations. Buffering By default, Tcl buffers data, so I/O is more efficient. The underlying device is accessed less frequently, so there is less overhead. In some cases you may want data to be visible immediately and buffering gets in the way. The following turns off all buffering: fconfigure fileID -buffering none Full buffering means that output data is accumulated until a buffer fills; then a write is performed. For reading, Tcl attempts to read a whole buffer each time more data is needed. The read-ahead for buffering will not block. The -buffersize parameter controls the buffer size: fconfigure fileID -buffering full -buffersize 8192 Line buffering is used by default on stdin and stdout. Each newline in an output channel causes a write operation. Read buffering is the same as full buffering. The following command turns on line buffering: fconfigure fileID -buffering line This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks End of Line Translations On UNIX, text lines end with a newline character (\n). On Macintosh they end with a carriage return (\r). On Windows they end with a carriage return, newline sequence (\r\n). Network sockets also use the carriage return, newline sequence. By default, Tcl accepts any of these, and the line terminator can even change within a channel. All of these different conventions are converted to the UNIX style so that once read, text lines always end with a newline character (\n). Both the read and gets commands do this conversion. By default, text lines are generated in the platform-native format during output. The default behavior is almost always what you want, but you can control the translation with fconfigure. Table 16-5 shows settings for -translation: Table 16-5. End of line translation modes binary No translation at all. lf UNIX-style, which also means no translations. cr Macintosh style. On input, carriage returns are converted to newlines. On output, newlines are converted to carriage returns. crlf Windows and Network style. On input, carriage return, newline is converted to a newline. On output, a newline is converted to a carriage return, newline. auto The default behavior. On input, all end of line conventions are converted to a newline. Output is in native format. End of File Character In DOS file systems, there may be a Control-z character (\x1a) at the end of a text file. By default, this character is ignored on the Windows platform if it occurs at the end of the file, and this character is output when you close the file. You can turn this off by specifying an empty string for the end of file character: fconfigure fileID -eofchar {} In Tcl 8.4 the end-of-file character trick is used by Tcl_EvalFile and source to allow Tclkit and other tools to append non-script data to script files. This is enabled by default, and should not normally interfere with your scripts. Serial Devices This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The -mode attribute specifies the baud rate, parity mode, the number of data bits, and the number of stop bits: set tty [open /dev/ttya] fconfigure $tty -mode => 9600,0,8,2 Tcl 8.4 added the enhanced control of serial channels for Windows and Unix systems. The options are listed inTable 16-4. Windows has some special device names that always connect you to the serial line devices when you use open. They are com1 through com9. To access com devices above 9, use this form:{\\.\comXX}. The Windows system console is namedcon. The Windows null device is nul. UNIX has names for serial devices in /dev. The serial devices are /dev/ttya, /dev/ttyb, and so on. The system console is/dev/console. The current terminal is /dev/tty. The null device is /dev/null. Macintosh needs a special command to open serial devices. This is provided by a third-party extension that you can find at the Tcl Resource Center under: http://www.tcl.tk/resource/software/extensions/macintosh/ Character Set Encodings Tcl automatically converts various character set encodings into Unicode internally. It cannot automatically detect the encoding for a file or network socket, however, so you need to use fconfigure -encoding if you are reading data that is not in the system's default encoding. Character set issues are explained in more detail in Chapter 15. Configuring Read-Write Channels If you have a channel that is used for both input and output, you can set the channel parameters independently for input and output. In this case, you can specify a two-element list for the parameter value. The first element is for the input side of the channel, and the second element is for the output side of the channel. If you specify only a single element, it applies to both input and output. For example, the following command forces output end of line translations to be crlf mode, leaves the input channel on automatic, and sets the buffer size for both input and output: fconfigure pipe -translation {auto crlf} -buffersize 4096 [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 17. Socket Programming This chapter shows how to use sockets for programming network clients and servers. Advanced I/O techniques for sockets are described, including nonblocking I/O and control over I/O uffering. Tcl commands discussed are: socket, fconfigure, and http::geturl. Sockets are network communication channels. The sockets described in this chapter use the TCP network protocol, although you can find Tcl extensions that create sockets using other protocols. TCP provides a reliable byte stream between two hosts connected to a network. TCP handles all the issues about routing information across the network, and it automatically recovers if data is lost or corrupted along the way. TCP is the basis for other protocols like Telnet, FTP, and HTTP. A Tcl script can use a network socket just like an open file or pipeline. Instead of using the Tcl open command, you use the socket command to open a socket. Then you use gets, puts, and read to transfer data. The close command closes a network socket. Network programming distinguishes between clients and servers. A server is a process or program that runs for long periods of time and controls access to some resource. For example, an FTP server governs access to files, and an HTTP server provides access to hypertext pages on the World Wide Web. A client typically connects to the server for a limited time in order to gain access to the resource. For example, when a Web browser fetches a hypertext page, it is acting as a client. The extended examples in this chapter show how to program the client side of the HTTP protocol. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] Networking Extensions for Tcl This chapter describes the basic programming techniques for sockets. Socket programing in Tcl is pretty easy, and a variety of extensions have been created to handle common protocols. This section reviews some of the packages that are available, and then the rest of the chapter describes how to program sockets yourself. Scotty The Scotty extension supports many network protocols. The Scotty Tcl extension provides access to other network protocols like UDP, DNS, and RPC. It also supports the SNMP network management protocol and the MIB database associated with SNMP. Scotty is a great extension package that is widely used for network management applications. It is a C-level extension, so you have to compile it yourself or find a binary distribution. Its home page is: http://wwwsnmp.cs.utwente.nl/~schoenw/scotty/ Standard Tcl Library The Standard Tcl Library (tcllib) has several packages that support widely used TCP-based protocols. These are all pure-Tcl implementations. There are packages for: DNS client. Map between hostnames and IP addresses. FTP client. Open FTP connections and download files from FTP servers. FTP server. Implement a simple, extensible FTP server. IRC client. Implement a chat client. NNTP client. Fetch news from a news server. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks POP3 client. Post Office Protocol lets you fetch email from mail servers. POP3 server. Implement a mail server. SMTP client. Send email via the SMTP protocol. SMTP server. Accept incoming email via SMTP. URI manipulation. Package for parsing URLs. There is good on-line documentation for these packages at: http://tcllib.sourceforge.net/tcllib/doc/ HTTP The Tcl distribution includes an HTTP client, which is described on page 251. You don't need to add tcllib to get this. In addition, there is a nice web server built in Tcl, which is the topic of Chapter 18. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Client Sockets A client opens a socket by specifying the host address and port number for the server of the socket. The host address gives the network location (i.e., which computer), and the port selects a particular server from all the possible servers that may be running on that host. For example, HTTP servers typically use port 80, while FTP servers use port 20. The following example shows how to open a client socket to a Web server: set s [socket www.tcl.tk 80] There are two forms for host names. The previous example uses a domain name: www.tcl.tk. You can also specify raw IP addresses, which are specified with four dot-separated integers (e.g., 192.220.75.86). A domain name is mapped into a raw IP address by the system software, and it is almost always a better idea to use a domain name in case the IP address assignment for the host changes. This can happen when hosts are upgraded or they move to a different part of the network. Some systems also provide symbolic names for well-known port numbers. For example, instead of using 20 for the FTP service, you can use ftp. On UNIX systems, the well-known port numbers are listed in the file named/etc/services. Client Socket Options The socket command accepts some optional arguments when opening the client-side socket. The general form of the command is: socket ?-async? ?-myaddr address? ?-myport myport? host port Ordinarily the address and port on the client side are chosen automatically. If your computer has multiple network interfaces, you can select one with the -myaddr option. The address value can be a domain name or an IP address. If your application needs a specific client port, it can choose one with the -myport option. If the port is in use, thesocket command will raise an error. The -async option causes connection to happen in the background, and thesocket command returns immediately. The socket becomes writable when the connection completes, or fails. You can use fileevent to get a callback when this occurs. This is shown inExample 17-1. If you use the socket before the connection completes, and the socket is in blocking mode, then Tcl automatically blocks and waits for the connection to complete. If the socket is in nonblocking mode, attempts to use the socket return immediately. The gets and read commands would return -1, and fblocked would return 1 in this situation. In some cases, it can take a long time to open the connection to the server. Usually this occurs when the server host is down, and it may take longer than you want for the connection to time out. The following example sets up a timer with after so that you can choose your own timeout limit on the connection: Example 17-1 Opening a client socket with a timeout . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than proc Socket_Client {host port timeout} { global connected after $timeout {set connected timeout} set sock [socket -async $host $port] fileevent $sock w {set connected ok} vwait connected fileevent $sock w {} if {$connected == "timeout"} { return -code error timeout } else { return $sock } } [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Server Sockets A TCP server socket allows multiple clients. The way this works is that the socket command creates a listening socket, and then new sockets are created when clients make connections to the server. Tcl takes care of all the details and makes this easy to use. You simply specify a port number and give the socket command a callback to execute when a client connects to your server socket. The callback is just a Tcl command. A simple example is shown below: Example 17-2 Opening a server socket set listenSocket [socket -server Accept 2540] proc Accept {newSock addr port} { puts "Accepted $newSock from $addr port $port" } vwait forever The Accept command is the callback made when clients connect to the server. Tcl adds additional arguments to the callback before it calls it. The arguments are the new socket connection, and the host and port number of the remote client. In this simple example, Accept just prints out its arguments. The vwait command puts Tcl into its event loop so that it can do the background processing necessary to accept connections. Thevwait command will wait until the forever variable is modified, which won't happen in this simple example. The key point is that Tcl processes other events (e.g., network connections and other file I/O) while it waits. If you have a Tk application (e.g., wish), then it already has an event loop to handle window system events, so you do not need to use vwait. The Tcl event loop is discussed on page 227. Server Socket Options By default, Tcl lets the operating system choose the network interface used for the server socket, and you simply supply the port number. If your computer has multiple interfaces, you may want to specify a particular one. Use the -myaddr option for this. The general form of the command to open server sockets is: socket -server callback ?-myaddr address? port The last argument to the socket command is the server's port number. For your own unofficial servers, you'll need to pick port numbers higher than 1024 to avoid conflicts with existing services. UNIX systems prevent user programs from opening server sockets with port numbers less than 1024. If you use 0 as the port number, then the operating system will pick the listening port number for you. You must use fconfigure to find out what port you have: fconfigure $sock -sockname => ipaddr hostname port This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] The Echo Service Example 17-3 The echo service proc Echo_Server {port} { global echo set echo(main) [socket -server EchoAccept $port] } proc EchoAccept {sock addr port} { global echo puts "Accept $sock from $addr port $port" set echo(addr,$sock) [list $addr $port] fconfigure $sock -buffering line fileevent $sock readable [list Echo $sock] } proc Echo {sock} { global echo if {[eof $sock] || [catch {gets $sock line}]} { # end of file or abnormal connection drop close $sock puts "Close $echo(addr,$sock)" unset echo(addr,$sock) } else { if {[string compare $line "quit"] == 0} { # Prevent new connections. # Existing connections stay open. close $echo(main) } puts $sock $line } } The echo server accepts connections from clients. It reads data from the clients and writes that data back. The example uses fileevent to wait for data from the client, and it uses fconfigure to adjust the buffering behavior of the network socket. You can useExample 17-3 as a template for more interesting services. The Echo_Server procedure opens the socket and saves the result inecho(main). When this socket is closed later, the server stops accepting new connections but existing connections won't be affected. If you want to experiment with this server, start it and wait for connections like this: Echo_Server 2540 vwait forever The EchoAccept procedure uses the fconfigure command to set up line buffering. This means that eachputs by the server results in a network transmission to the client. The importance of this will be described in more detail later. A complete description of the fconfigure command is This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks given in Chapter 16. The EchoAccept procedure uses the fileevent command to register a procedure that handles I/O on the socket. In this example, the Echo procedure will be called whenever the socket is readable. Note that it is not necessary to put the socket into nonblocking mode when using the fileevent callback. The effects of nonblocking mode are discussed on page 232. EchoAccept saves information about each client in theecho array. This is used only to print out a message when a client closes its connection. In a more sophisticated server, however, you may need to keep more interesting state about each client. The name of the socket provides a convenient handle on the client. In this case, it is used as part of the array index. The Echo procedure first checks to see whether the socket has been closed by the client or there is an error when reading the socket. Theif expression only performs the gets if the eof does not return true: if {[eof $sock] || [catch {gets $sock line}]} { Closing the socket automatically clears the fileevent registration. If you forget to close the socket upon the end of file condition, the Tcl event loop will invoke your callback repeatedly. It is important to close it when you detect end of file. Example 17-4 A client of the echo service proc Echo_Client {host port} { set s [socket $host $port] fconfigure $s -buffering line return $s } set s [Echo_Client localhost 2540] puts $s "Hello!" gets $s => Hello! In the normal case, the server simply reads a line with gets and then writes it back to the client withputs. If the line is "quit," then the server closes its main socket. This prevents any more connections by new clients, but it doesn't affect any clients that are already connected. Example 17-4 shows a sample client of the Echo service. The main point is to ensure that the socket is line buffered so that each puts by the client results in a network transmission. (Or, more precisely, each newline character results in a network transmission.) If you forget to set line buffering with fconfigure, the client's gets command will probably hang because the server will not get any data; it will be stuck in buffers on the client. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Fetching a URL with HTTP The HyperText Transport Protocol (HTTP) is the protocol used on the World Wide Web. This section presents a procedure to fetch pages or images from a server on the Web. Items in the Web are identified with a Universal Resource Location (URL) that specifies a host, port, and location on the host. The basic outline of HTTP is that a client sends a URL to a server, and the server responds with some header information and some content data. The header information describes the content, which can be hypertext, images, postscript, and more. Example 17-5 Opening a connection to an HTTP server proc Http_Open {url} { global http if {![regexp -nocase {^(http://)?([^:/]+)(:([0-9]+))?(/.*)} \ $url x protocol server y port path]} { error "bogus URL: $url" } if {[string length $port] == 0} { set port 80 } set sock [socket $server $port] puts $sock "GET $path HTTP/1.0" puts $sock "Host: $server" puts $sock "User-Agent: Tcl/Tk Http_Open" puts $sock "" flush $sock return $sock } The Http_Open procedure uses regexp to pick out the server and port from the URL. This regular expression is described in detail on page 159. The leading http:// is optional, and so is the port number. If the port is left off, then the standard port 80 is used. If the regular expression matches, then a socket command opens the network connection. The protocol begins with the client sending a line that identifies the command (GET), the path, and the protocol version. The path is the part of the URL after the server and port specification. The rest of the request is lines in the following format: key: value The Host identifies the server, which supports servers that implement more than one server name. The User-Agent identifies the client program, which is often a browser like Netscape Navigator, Mozilla, or Internet Explorer. The key-value lines are terminated with a blank line. This data is flushed out of the Tcl buffering system with the flush command. The server will respond by sending the URL contents back over the socket. This is described shortly, but first we consider proxies. Proxy Servers This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A proxy is used to get through firewalls that many organizations set up to isolate their network from the Internet. The proxy accepts HTTP requests from clients inside the firewall and then forwards the requests outside the firewall. It also relays the server's response back to the client. The protocol is nearly the same when using the proxy. The difference is that the complete URL is passed to the GET command so that the proxy can locate the server. Example 17-6 uses a proxy if one is defined: Example 17-6 Opening a connection through a HTTP proxy # Http_Proxy sets or queries the proxy proc Http_Proxy {{new {}}} { global http if ![info exists http(proxy)] { return {} } if {[string length $new] == 0} { return $http(proxy):$http(proxyPort) } else { regexp {^([^:]+):([0-9]+)$} $new x \ http(proxy) http(proxyPort) } } proc Http_Open {url {cmd GET} {query {}}} { global http if {![regexp -nocase {^(http://)?([^:/]+)(:([0-9]+))?(/.*)} \ $url x protocol server y port path]} { error "bogus URL: $url" } if {[string length $port] == 0} { set port 80 } if {[info exists http(proxy)] && [string length $http(proxy)]} { set sock [socket $http(proxy) $http(proxyPort)] puts $sock "$cmd http://$server:$port$path HTTP/1.0" } else { set sock [socket $server $port] puts $sock "$cmd $path HTTP/1.0" } puts $sock "User-Agent: Tcl/Tk Http_Open" puts $sock "Host: $server" if {[string length $query] > 0} { puts $sock "Content-Length: [string length $query]" puts $sock "" puts $sock $query } puts $sock "" flush $sock fconfigure $sock -blocking 0 return $sock } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The HEAD Request In Example 17-6, the Http_Open procedure takes a cmd parameter so that the user ofHttp_Open can perform different operations. TheGET operation fetches the contents of a URL. The HEAD operation just fetches the description of a URL, which is useful to validate a URL. The POST operation transmits query data to the server (e.g., values from a form) and also fetches the contents of the URL. All of these operations follow a similar protocol. The reply from the server is a status line followed by lines that have key-value pairs. This format is similar to the client's request. The reply header is followed by content data with GET and POST operations. Example 17-7 implements the HEAD command, which does not involve any reply data: Example 17-7 Http_Head validates a URL proc Http_Head {url} { upvar #0 $url state catch {unset state} set state(sock) [Http_Open $url HEAD] fileevent $state(sock) readable [list HttpHeader $url] # Specify the real name, not the upvar alias, to vwait vwait $url\(status) catch {close $state(sock)} return $state(status) } proc HttpHeader {url} { upvar #0 $url state if {[eof $state(sock)]} { set state(status) eof close $state(sock) return } if {[catch {gets $state(sock) line} nbytes]} { set state(status) error lappend state(headers) [list error $nbytes] close $state(sock) return } if {$nbytes < 0} { # Read would block return } elseif {$nbytes == 0} { # Header complete set state(status) head } elseif {![info exists state(headers)]} { # Initial status reply from the server set state(headers) [list http $line] } else { # Process key-value pairs regexp {^([^:]+): *(.*)$} $line x key value lappend state(headers) [string tolower $key] $value This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks } } The Http_Head procedure uses Http_Open to contact the server. The HttpHeader procedure is registered as afileevent handler to read the server's reply. A global array keeps state about each operation. The URL is used in the array name, and upvar is used to create an alias to the name (upvar is described on page 92): upvar #0 $url state You cannot use the upvar alias as the variable specified tovwait. Instead, you must use the actual name. The backslash turns off the array reference in order to pass the name of the array element to vwait, otherwise Tcl tries to reference url as an array: vwait $url\(status) The HttpHeader procedure checks for special cases: end of file, an error on thegets, or a short read on a nonblocking socket. The very first reply line contains a status code from the server that is in a different format than the rest of the header lines: code message The code is a three-digit numeric code. 200 is OK. Codes in the 400's and 500's indicate an error. The codes are explained fully in RFC 1945 that specifies HTTP 1.0. The first line is saved with the key http: set state(headers) [list http $line] The rest of the header lines are parsed into key-value pairs and appended onto state(headers). This format can be used to initialize an array: array set header $state(headers) When HttpHeader gets an empty line, the header is complete and it sets thestate(status) variable, which signals Http_Head. Finally, Http_Head returns the status to its caller. The complete information about the request is still in the global array named by the URL. Example 17-8 illustrates the use of Http_Head: Example 17-8 Using Http_Head set url http://www.sun.com/ set status [Http_Head $url] => eof upvar #0 $url state array set info $state(headers) parray info info(http) HTTP/1.0 200 OK info(server) Apache/1.1.1 info(last-modified) Nov ... info(content-type) text/html The GET and POST Requests Example 17-9 shows Http_Get, which implements the GET and POST requests. The difference between these is thatPOST sends query data to the server after the request header. Both operations get a reply from the server that is divided into a descriptive header and the content This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks data. The Http_Open procedure sends the request and the query, if present, and reads the reply header.Http_Get reads the content. The descriptive header returned by the server is in the same format as the client's request. One of the key-value pairs returned by the server specifies the Content-Type of the URL. The content-types come from the MIME standard, which is described in RFC 1521. Typical content-types are: text/html — HyperText Markup Language (HTML), which is introduced inChapter 3. text/plain — plain text with no markup. image/gif — image data in GIF format. image/jpeg — image data in JPEG format. application/postscript — a postscript document. application/x-tcl — a Tcl program! This type is discussed inChapter 20. Example 17-9 Http_Get fetches the contents of a URL proc Http_Get {url {query {}}} { upvar #0 $url state ;# Alias to global array catch {unset state} ;# Aliases still valid. if {[string length $query] > 0} { set state(sock) [Http_Open $url POST $query] } else { set state(sock) [Http_Open $url GET] } set sock $state(sock) fileevent $sock readable [list HttpHeader $url] # Specify the real name, not the upvar alias, to vwait vwait $url\(status) set header(content-type) {} set header(http) "500 unknown error" array set header $state(headers) # Check return status. # 200 is OK, other codes indicate a problem. regsub "HTTP/1.. " $header(http) {} header(http) if {![string match 2* $header(http)]} { catch {close $sock} if {[info exists header(location)] && [string match 3* $header(http)]} { # 3xx is a redirection to another URL set state(link) $header(location) return [Http_Get $header(location) $query] } return -code error $header(http) } # Set up to read the content data switch -glob -- $header(content-type) { text/* { This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks # Read HTML into memory fileevent $sock readable [list HttpGetText $url] } default { # Copy content data to a file fconfigure $sock -translation binary set state(filename) [File_TempName http] if [catch {open $state(filename) w} out] { set state(status) error set state(error) $out close $sock return $header(content-type) } set state(fd) $out fcopy $sock $out -command [list HttpCopyDone $url] } } vwait $url\(status) return $header(content-type) } Http_Get uses Http_Open to initiate the request, and then it looks for errors. It handles redirection errors that occur if a URL has changed. These have error codes that begin with 3. A common case of this error is when a user omits the trailing slash on a URL (e.g., http://www.tcl.tk). Most servers respond with: 302 Document has moved Location: http://www.tcl.tk/ If the content-type is text, then Http_Get sets up a fileevent handler to read this data into memory. The socket is in nonblocking mode, so the read handler can read as much data as possible each time it is called. This is more efficient than using gets to read a line at a time. The text will be stored in the state(body) variable for use by the caller ofHttp_Get. Example 17-10 shows the HttpGetText fileevent handler: Example 17-10 HttpGetText reads text URLs proc HttpGetText {url} { upvar #0 $url state if {[eof $state(sock)]} { # Content complete set state(status) done close $state(sock) } elseif {[catch {read $state(sock)} block]} { set state(status) error lappend state(headers) [list error $block] close $state(sock) } else { append state(body) $block } } The content may be in binary format. This poses a problem for Tcl 7.6 and earlier. A null character will terminate the value, so values with embedded nulls cannot be processed safely by Tcl scripts. Tcl 8.0 supports strings and variable values with arbitrary binary data. Example 17-9 uses fcopy to copy data from the socket to a file without storing it in Tcl variables. This command was introduced in Tcl 7.5 as unsupported0, and became fcopy in Tcl 8.0. It takes a callback argument that is invoked when the copy is complete. The callback gets This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks additional arguments that are the bytes transferred and an optional error string. In this case, these arguments are added to the url argument specified in the fcopy command. Example 17-11 shows the HttpCopyDone callback: Example 17-11 HttpCopyDone is used with fcopy proc HttpCopyDone {url bytes {error {}}} { upvar #0 $url state if {[string length $error]} { set state(status) error lappend state(headers) [list error $error] } else { set state(status) ok } close $state(sock) close $state(fd) } The user of Http_Get uses the information in thestate array to determine the status of the fetch and where to find the content. There are four cases to deal with: There was an error, which is indicated by the state(error) element. There was a redirection, in which case, the new URL is in state(link). The client of Http_Get should change the URL and look at its state instead. You can use upvar to redefine the alias for thestate array: upvar #0 $state(link) state There was text content. The content is in state(body). There was another content-type that was copied to state(filename). The fcopy Command The fcopy command can do a complete copy in the background. It automatically sets upfileevent handlers, so you do not have to usefileevent yourself. It also manages its buffers efficiently. The general form of the command is: fcopy input output ?-size size? ?-command callback? The -command argument makes fcopy work in the background. When the copy is complete or an error occurs, thecallback is invoked with one or two additional arguments: the number of bytes copied, and, in the case of an error, it is also passed an error string: fcopy $in $out -command [list CopyDone $in $out] proc CopyDone {in out bytes {error {}} { close $in ; close $out } With a background copy, the fcopy command transfers data from input until end of file or size bytes have been transferred. If no-size argument is given, then the copy goes until end of file. It is not safe to do other I/O operations with input or output during a background fcopy. If either This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks input or output gets closed while the copy is in progress, the current copy is stopped. If theinput is closed, then all data already queued for output is written out. Without a -command argument, the fcopy command reads as much as possible depending on the blocking mode ofinput and the optional size parameter. Everything it reads is queued for output before fcopy returns. If output is blocking, then fcopy returns after the data is written out. If input is blocking, then fcopy can block attempting to read size bytes or until end of file. The fcopy command had a bug which ignored the encoding on the channels which was corrected in 8.3.4. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The http Package The standard Tcl library includes an http package that is based on the code I wrote for this chapter. This section documents the package, which has a slightly different interface. The library version uses namespaces and combines the Http_Get, Http_Head, and Http_Post procedures into a single http::geturl procedure. The examples in this chapter are still interesting, but you should use the standardhttp package for your production code. http::config The http::config command is used to set the proxy information, time-outs, and theUser-Agent and Accept headers that are generated in the HTTP request. You can specify the proxy host and port, or you can specify a Tcl command that is run to determine the proxy. With no arguments, http::config returns the current settings: http::config => -accept */* -proxyfilter http::ProxyRequired -proxyhost {} -proxyport {} -useragent {Tcl http client package 2.4} If you specify just one option, its value is returned: http::config -proxyfilter => http::ProxyRequired You can set one or more options: http::config -proxyhost webcache.eng -proxyport 8080 The default proxy filter just returns the -proxyhost and -proxyport values if they are set. You can supply a smarter filter that picks a proxy based on the host in the URL. The proxy filter is called with the hostname and should return a list of two elements, the proxy host and port. If no proxy is required, return an empty list. http::geturl The http::geturl procedure does a GET, POST, or HEAD transaction depending on its arguments. By default,http::geturl blocks until the request completes and it returns a token that represents the transaction. As described below, you use the token to get the results of the transaction. If you supply a -command callback option, then http::geturl returns immediately and invokes callback when the transaction This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks completes. The callback is passed the token that represents the transaction. For simple applications you can simply block on the transaction: set token [http::geturl www.beedub.com/index.html] => http::1 The leading http:// in the URL is optional. The return value is a token that represents the transaction. There are other http:: commands that return information when passed the token. The token is also the name of an array that contains state about the transaction. Make sure to clean up this array to free memory when you are done: http::cleanup $token If you need to access the array directly, use upvar to create an alias: upvar #0 $token data Table 17-1 lists the options to http::geturl. Table 17-1. Options to the http::geturl command -binary boolean Specifies whether we should do a binary transfer of the data. (Tcl 8.3) -blocksize num Block size when copying to a channel. -channel fileID The fileID is an open file or socket. The URL data is copied to this channel instead of saving it in memory. -command callback Calls callback when the transaction completes. The token fromhttp::geturl is passed to callback. -handler command Called from the event handler to read data from the URL. -headers list The list specifies a set of headers that are included in the HTTP request. The list alternates between header keys and values. -progress command Calls command after each block is copied to a channel. It gets called with three parameters: command token totalsize currentsize -query codedstring Issues a POST request with the codedstring form data. -queryblocksize num Block size when copying to the query channel. -querychannel fileID The fileID is an open file or socket. The query data is copied from this channel instead of passed in a string. -queryprogress Calls command after each block is copied from the query channel. It gets called with three parameters: command command token totalsize currentsize -timeout msec Aborts the request after msec milliseconds have elapsed. -type mime-type Use mime-type as the Content-Type value during a POST operation. -validate bool If bool is true, a HEAD request is made. Table 17-2 lists the access functions to the state array. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 17-2. The http support procedures http::cleanup $token Unsets the state array named by$token. http::code $token Returns state(http). http::data $token Returns state(body). http::error $token Returns state(error). http::ncode $token Returns the numeric return code contained instate(http). http::size $token Return the number of bytes read from the URL so far. http::status $token Returns state(status). http::wait $token Blocks until the transaction completes. The array elements are listed in Table 17-3: Table 17-3. Elements of the http::geturl state array body The contents of the URL. charset The value of the charset attribute from the Content-Type meta-data value. If none was specified, this defaults to the RFC standard iso8859-1. coding A copy of the Content-Encoding meta-data value. currentsize The current number of bytes transferred. error An explanation of why the transaction was aborted. http The HTTP reply status. meta A list of the keys and values in the reply header. posterror An explanation of why the transaction was aborted when writing post query data, if any. status The current status: pending, ok, eof, or reset. totalsize The expected size of the returned data. type The content type of the returned data. url The URL of the request. You can take advantage of the asynchronous interface by specifying a command that is called when the transaction completes. The callback is passed the token returned from http::geturl so that it can access the transaction state: http::geturl $url -command [list Url_Display $text $url] proc Url_Display {text url token} { upvar #0 $token state This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Display the url in text } You can have http::geturl copy the URL to a file or socket with the-channel option. This is useful for downloading large files or images. In this case, you can get a progress callback so that you can provide user feedback during the transaction. Example 17-12 shows a simple downloading script: Example 17-12 Downloading files with http::geturl #!/usr/local/bin/tclsh8.4 if {$argc < 2} { puts stderr "Usage: $argv0 url file" exit 1 } package require http set url [lindex $argv 0] set file [lindex $argv 1] set out [open $file w] proc progress {token total current} { puts -nonewline "." } http::config -proxyhost webcache.eng -proxyport 8080 set token [http::geturl $url -progress progress \ -headers {Pragma no-cache} -channel $out] close $out # Print out the return header information puts "" upvar #0 $token state puts $state(http) foreach {key value} $state(meta) { puts "$key: $value" } exit 0 http::formatQuery If you specify form data with the -query option, then http::geturl does a POST transaction. You need to encode the form data for safe transmission. The http::formatQuery procedure takes a list of keys and values and encodes them inx-www-url-encoded format. Pass this result as the query data: http::formatQuery name "Brent Welch" title "Tcl Programmer" => name=Brent+Welch&title=Tcl+Programmer This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks http::register and http::unregister The http::register procedure registers a protocol handler for URL protocols other than HTTP. Thehttp::unregister procedure removes the handler registration. The primary application is to provide secure web access via HTTPS and the TLS extension. package require tls http::register https 443 ::tls::socket set token [http::geturl https://my.secure.site/] http::reset You can cancel an outstanding transaction withhttp::reset: http::reset $token This is done automatically when you setup a-timeout with http::config. http::cleanup When you are done with the data returned fromhttp::geturl, use the http::cleanup procedure to unset the state variable used to store the data. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Basic Authentication Web pages are often password protected. The most common form of this uses a protocol called Basic Authentication, which is not very strong, but easy to implement. With this scheme, the server responds to an HTTP request with a 401 error status and a Www-Authenticate header, which specifies the authentication protocol the server wants to use. For example, the server response can contain the following information: HTTP/1.0 401 Authorization Required Www-Authenticate: Basic realm="My Pages" The realm is meant to be an authentication domain. In practice, it is used in the string that gets displayed to the user as part of the password prompt. For example, a Web browser will display this prompt: Enter the password for My Pages at www.beedub.com After getting the user name and password from the user, the Web browser tries its HTTP request again. This time it includes an Authorization header that contains the user name and password encoded with base64 encoding. There is no encryption at all — anyone can decode the string, which is why this is not a strong form of protection. The Standard Tcl Library includes a base64 package that has base64::encode and base64::decode procedures. Example 17-13 illustrates the Basic Authentication protocol. It uses the-headers option to http::geturl that lets you pass additional headers in the request. Example 17-13 Basic Authentication using http::geturl package require base64 package require http proc BasicAuthentication {url promptProc} { set token [http::geturl $url] http::wait $token if {[string match *401* [http::code $token]]} { upvar #0 $token data # Extract the realm from the Www-Authenticate line array set reply $data(meta) if {[regexp {realm=(.*)} $reply(Www-Authenticate) \ x realm]} { # Call back to prompt for username, password set answer [$promptProc $realm] http::cleanup $token # Encode username:password and pass this in # the Authorization header set auth [base64::encode \ This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [lindex $answer 0]:[lindex $answer 1]] set token [http::geturl $url -headers \ [list Authorization "Basic $auth"]] http::wait $token } } return $token } Example 17-13 takes a promptProc argument that is the name of a procedure to call to get the username and password. This procedure could display a Tk dialog box, or prompt for user input from the terminal. In practice, you probably already know the username and password. In this case, you can skip the initial challenge–response steps and simply supply the Authorization header on the first request: http::geturl $url -headers \ [list Authorization \ "Basic [base64::encode $username:$password]"] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 18. TclHttpd Web Server This chapter describes TclHttpd, a Web server built entirely in Tcl. The Web server can be used as a standalone server, or it can be embedded into applications to Web-enable them. TclHttpd provides a Tcl+HTML template facility that is useful for maintaining site-wide look and feel, and an Application Direct URL that invokes a Tcl procedure in an application. TclHttpd started out as about 175 lines of Tcl that could serve up HTML pages and images. The Tcl socket and I/O commands make this easy, and the C language implementation of the Tcl runtime library makes the server surprisingly fast. Of course, there are lots of features in Web servers like Apache or Netscape that were not present in the first prototype. Steve Uhler took my prototype, refined the HTTP handling, and aimed to keep the basic server under 250 lines. I went the other direction, setting up a modular architecture, adding in features found in other Web servers, and adding some interesting ways to connect TclHttpd to Tcl applications. Today TclHttpd is used both as a general-purpose Web server, and as a framework for building server applications. It implements www.tcl.tk and a number of other general purpose Web sites. It is also built into several commercial applications such as license servers and mail spam filters. The server is freely available, just like Tcl itself, and you can use it in any application without restriction or license fees. Instructions for setting up the TclHttpd on your platform are given toward the end of the chapter, on page 284. It works on Unix, Windows, and Macintosh. Using TclHttpd, you can have your own Web server up and running quickly. This chapter provides an overview of the server and several examples of how you can use it. The chapter is not an exhaustive reference to every feature. Instead, it concentrates on a very useful subset of server features that I use the most. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Integrating TclHttpd with Your Application The bulk of this chapter describes the various ways you can extend the server and integrate it into your application. TclHttpd is interesting because, as a Tcl script, it is easy to add to your application. Suddenly your application has an interface that is accessible to Web browsers in your company's intranet or the global Internet. The Web server provides several ways you can connect it to your application: Static pages — As a "normal" Web server, you can serve static documents that describe your application. Domain handlers — You can arrange for all URL requests in a section of your Web site to be handled by your application. This is a very general interface where you interpret what the URL means and what sort of pages to return to each request. For example, http://www.tcl.tk/resource is implemented this way. The URL past/resource selects an index in a simple database, and the server returns a page describing the pages under that index. Application Direct URLs — This is a domain handler that maps URLs onto Tcl procedures. The form query data that is part of the HTTP GET or POST request is automatically mapped onto the parameters of the Application Direct procedure. The procedure simply computes the page as its return value. This is an elegant and efficient alternative to the CGI interface. For example, in TclHttpd, the URLs under /status report various statistics about the Web server's operation. Document handlers — You can define a Tcl procedure that handles all files of a particular type. For example, the server has a handler for CGI scripts, HTML files, image maps, and HTML+Tcl template files. HTML+Tcl Templates — These are Web pages that mix Tcl and HTML markup. The server replaces the Tcl using thesubst command and returns the result. The server can cache the result in a regular HTML file to avoid the overhead of template processing on future requests. Templates are a great way to maintain the common look and feel to a family of Web pages, as well as to implement more advanced dynamic HTML features like self-checking forms. TclHttpd Architecture You may find it helpful to read the code to learn more about the features of the server. In this section, there are references to Tcl files in the source, which are in the lib directory of the distribution that is on the CD-ROM. Figure 18-1 shows the basic components of the server. At the core is theHttpd module (httpd.tcl), which implements the server side of the HTTP protocol. The "d" in Httpd stands fordaemon, which is the name given to system servers onUNIX. This module manages network requests, dispatches them to the Url module, and provides routines used to return the results to requests. Figure 18-1. The dotted box represents one application that embeds TclHttpd. Document templates and Application Direct URLs provide direct connections from an HTTP request to your application. You can also implement completely custom URL handlers. . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The Url module (url.tcl) divides the Web site into domains, which are subtrees of the URL hierarchy provided by the server. The idea is that different domains may have completely different implementations. For example, the Document domain (doc.tcl) maps its URLs into files and directories on your hard disk, while the Application Direct domain (direct.tcl) maps URLs into Tcl procedure calls within your application. The CGI domain (cgi.tcl) maps URLs onto other programs that compute Web pages. Adding Code to TclHttpd The TclHttpd distribution, which is described in more detail starting at page 284, is set up so you can easily add code for your application into the server. For simple applications, you simply put your files into a special directory for custom code, and the server loads them automatically upon startup. These files should define Tcl procedures and register them as Domain Handlers, Direct URL handlers, or Document handlers. Example 18-1 implements /hello/world: Example 18-1 The hello.tcl file implements /hello/world Direct_Url /hello Hello proc Hello/world {} { return "<b>Hello, World!</b>" } Suppose you put that file into the directory /tmp/tclhttpd_test. Then you can start the server like this: tclsh8.3 bin/httpd.tcl -library /tmp/tclhttpd_test -debug 1 Now access this URL: . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks http://localhost:8015/hello/world Custom Main Programs The TclHttpd main program, bin/httpd.tcl, may conflict with the main program of your existing application. For those applications that embed Tcl interpreters in a more custom manner, you will need to modify bin/httpd.tcl for use with your application. That script is not very big, and it is well-commented. The key elements are the Httpd_Server call that opens the listening socket for the Web server, and thevwait at the very end that activates the event loop. The rest is all about argument parsing and initializing the various modules that support the server. It is those aspects that may differ for your custom server application. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Domain Handlers You can implement new kinds of domains that provide your own interpretation of a URL. This is the most flexible interface available to extend the Web server. You provide a callback that is invoked to handle every request in a domain, or subtree, of the URL hierarchy. The callback interprets the URL, computes the page content, and returns the data using routines from the Httpd module. Example 18-2 defines a simple domain that always returns the same page to every request. The domain is registered with the Url_PrefixInstall command. The arguments to Url_PrefixInstall are the URL prefix and a callback that is called to handle all URLs that match that prefix. In the example, all URLs that have the prefix /simple are dispatched to the SimpleDomain procedure. The SimpleDomain handler illustrates several properties of domain handlers. The sock and suffix arguments to SimpleDomain are appended by Url_Dispatch when it invokes the domain handler. Thesock is the socket connection to the client. The suffix parameter is the part of the URL after the prefix. For example, if the server receives a request for the URL /simple/page, then the prefix is/simple and the suffix is /page. The prefix argument is defined when the callback is registered withUrl_PrefixInstall. You can specify whatever information you need to pass to the domain handler. In this simple example, we probably don't need the prefix, but if you implement several different URL domains with the same handler, then you can pass in the prefix to distinguish them. Example 18-2 A simple URL domain Url_PrefixInstall /simple [list SimpleDomain /simple] proc SimpleDomain {prefix sock suffix} { upvar #0 Httpd$sock data # Generate page header set html "<title>A simple page</title>\n" append html "<h1>$prefix$suffix</h1>\n" append html "<h1>Date and Time</h1>\n" append html [clock format [clock seconds]] # Display connection state append html "<h1>Connection State</h1>" append html [html::tableFromArray data border=1] # Display query data if {[info exist data(query)]} { append html "<h1>Query Data</h1>\n" append html [html::tableFromList [ncgi::nvlist] border=1] } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Httpd_ReturnData $sock text/html $html } Connection State and Query Data The sock parameter is a handle on the socket connection to the remote client. This variable is also used to name a state variable that the Httpd module maintains about the connection. The name of the state array is Httpd$sock. In some cases, you may need access to this information, and the standard idiom is to use upvar to get a more convenient name for this array (i.e.,data): upvar #0 Httpd$sock data The html and ncgi Packages The html package provides many procedures useful for generating fragments of HTML. Thehtml::tableFromArray procedure is used to dump out the connection state in the data array. Its cousin,html::tableFromList, is used to dump out the query data. The query data is obtained with the ncgi::nvlist procedure. TclHttpd initializes the ncgi module so you can use ncgi::nvlist, ncgi::value, and other procedures to access query data in your domain handlers. Note: it is not necessary to call ncgi::parse as you would from a CGI script. Thehtml package has some other features, which are described later, that are very useful when generating HTML forms. These packages are part of the Standard Tcl Library, tcllib, which can be found along with Tcl and TclHttpd. Returning Results Finally, once the page has been computed, the Httpd_ReturnData procedure is used to return the page to the client. This takes care of the HTTP protocol as well as returning the data. There are three related procedures, Httpd_ReturnFile, Httpd_Error, and Httpd_Redirect. These are summarized in Table 18-1 on page 277. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Application Direct URLs The Application Direct domain implementation provides the simplest way to extend the Web server. It hides the details associated with query data, decoding URL paths, and returning results. All you do is define Tcl procedures that correspond to URLs. Their arguments are automatically matched up to the query data, as shown in Example 13-3 on page 189. The Tcl procedures compute a string that is the result data, which is usually HTML. That's all there is to it. The name of the Tcl procedure that implements an Application Direct URL is related to the name of the URL. This way, TclHttpd can automatically look up the Tcl procedure that should implement a given URL. The Tcl procedure name and the URL have distinct prefixes, but the suffix is the same. For example, if the Tcl procedure prefix is Demo and the URL prefix is/demo, then the Demo/time Tcl procedure implements the /demo/time URL. The Direct_Url procedure sets up the correspondence between the procedures and URLs. This is shown in Example 18-3: Example 18-3 Application Direct URLs Direct_Url /demo Demo proc Demo {} { return "<html><head><title>Demo page</title></head>\n\ <body><h1>Demo page</h1>\n\ <a href=/demo/time>What time is it?</a>\n\ <form action=/demo/echo>\n\ Data: <input type=text name=data>\n\ <br>\n\ <input type=submit name=echo value='Echo Data'>\n\ </form>\n\ </body></html>" } proc Demo/time {{format "%H:%M:%S"}} { return [clock format [clock seconds] -format $format] } proc Demo/echo {args} { # Compute a page that echoes the query data set html "<head><title>Echo</title></head>\n" append html "<body>" append html [html::tableFromList $args "border=1"] return $html } Example 18-3 defines /demo as an Application Direct URL domain that is implemented by procedures that begin withDemo. There are just three URLs defined: /demo /demo/time /demo/echo The /demo page displays a hypertext link to the /demo/time page and a simple form that will be handled by the/demo/echo page. This page is This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks static, so there is just one return command in the procedure body. Each line of the string ends with: \n\ This is just a formatting trick to let me indent each line in the procedure, without having the line indented in the resulting string. Actually, the \-newline will be replaced by one space, so each line will be indented one space. You can leave those off and the page will display the same in the browser, but when you view the page source, you'll see the indenting. Or you could not indent the lines in the string, but then your code looks somewhat odd. The /demo/time procedure just returns the result ofclock format. It doesn't even bother adding<html>, <head>, or <body> tags, which you can get away with in today's browsers. A simple result like this is also useful if you are using programs to fetch information via HTTP requests to your application. Using Query Data Application Direct URL handlers have their parameters automatically assigned to values from the query data. Like any Tcl procedure, your Application Direct URL procedure can have named parameters, named parameters with default values, and the args parameter. The server matches the names of form values with names of your procedure parameters in order to assign their values. There are three cases: The name of the procedure parameter matches the name of a query data item. The query value is assigned to the parameter. The name of the procedure parameter does not appear in the query data. The parameter is assigned the empty string or its default value, if it has one. The /demo/time procedure is defined with an optional format argument. If a format value is present in the query data, then it overrides the default value given in the procedure definition. The query data item does not match any of the parameters. If the procedure has an args parameter as its last parameter, then the name and value of the query data item are appended to the args value. Otherwise, the query value is simply ignored. For example, the /demo/echo procedure's args parameter gets filled in with a name-value list of all query data. You can see that missing arguments or extra arguments do not cause errors. If you want to do strict parameter checking, then just use args and check the name-value query list yourself. Here is another example to illustrate the different ways that form data is assigned to procedure parameters. Suppose you have an Application Direct procedure declared like this: proc Demo/param { a b {c cdef} args} { body } You could create an HTML form that had elements named a, b, and c, and specified /demo/param for theACTION parameter of the FORM tag. Or you could type the following into your browser to embed the query data right into the URL: /demo/param?a=5&b=7&c=red&d=%7ewelch&e=two+words The ? separates the query data from the URL, and each query item is separated by&. In this case, when your procedure is called,a is 5, b is 7, c is red, and the args parameter becomes a list of: d ~welch e {two words} The %7e and the + are special codes for nonalphanumeric characters in the query data. The+ becomes a space, and the%xx sequence is replaced by the character with character code xx (e.g., %7e becomes ~). Normally, this encoding is taken care of automatically by the Web browser when it gets data from a form and passes it to the Web server. However, if you type query data directly or format URLs with complex query data in them, then you need to encode special values as we did here. Use the Url_Encode procedure to encode URLs that you put into Web pages. The Web server automatically decodes the values as it makes the assignments to the Application Direct URL procedure parameters. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than If a parameter does not match the query data, it gets its default value from the procedure definition, or it gets the empty string. Consider this example: /demo/param?b=5 In this case, a is "", b is 5, c is cdef, and args is an empty list. Returning Other Content Types The default content type for Application Direct URLs is text/html. You can specify other content types by using a global variable with the same name as your procedure. (Yes, this is a crude way to craft an interface.) Example 18-4 shows part of the faces.tcl file that implements an interface to a database of picons — personal icons — that is organized by user and domain names. The idea is that the database contains images corresponding to your email correspondents. The Faces_ByEmail procedure, which is not shown, looks up an appropriate image file. The Application Direct procedure is Faces/byemail, and it sets the global variableFaces/byemail to the correct Content-Type value based on the filename extension. The mapping from extension to content type is implemented by the Mtype procedure (mtype.tcl). MIME is the multimedia content standard for email, and it originated the various content types now also used in HTTP, hence the term "MIME type." Example 18-4 Alternate types for Application Direct URLs Direct_Url /faces Faces proc Faces/byemail {email} { global Faces/byemail set filename [Faces_ByEmail $email] set Faces/byemail [Mtype $filename] set in [open $filename] fconfigure $in -translation binary set X [read $in] close $in return $X } [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Document Types The Document domain (doc.tcl) maps URLs onto files and directories. It provides more ways to extend the server by registering different document type handlers. You can make up new types to support your application. Example 18-5 shows the pieces needed to create a handler for a fictitious document type application/myjunk that is invoked to handle files with the.junk suffix. Use the Mtype_Add procedure to register the mapping from file suffix to document type: Example 18-5 A sample document type handler # Register the mapping from suffix to MIME type Mtype_Add application/myjunk .junk # Define the document handler procedure # path is the name of the file on disk # suffix is part of the URL after the domain prefix # sock is the handle on the client connection proc Doc_application/myjunk {path suffix sock} { upvar #0 Httpd$sock data # data(url) is more useful than the suffix parameter. # Use the contents of file $path to compute a page set contents [somefunc $path] # Determine your content type set type text/html # Return the page Httpd_ReturnData $sock $type $data } The server finds the document handler in a two-step process. First, the type of a file is determined by its suffix. The mime.types file contains a map from suffixes to MIME types such as text/html or image/gif. This map is controlled by theMtype module in mtype.tcl. Second, the server checks for a Tcl procedure with the appropriate name: Doc_mimetype The matching procedure, if any, is called to handle the URL request. The procedure should use routines in the Httpd module to return data for the request. If there is no matching Doc_mimetype procedure, then the default document handler usesHttpd_ReturnFile and specifies the Content Type based on the file extension. This is the heart of the default document handler: Httpd_ReturnFile $sock [Mtype $path] $path As another example, the HTML+Tcl templates use the .tml suffix that is mapped to the application/x-tcl-template type. You can find the document handler Doc_application/x-tcl-template in doc.tcl. The TclHttpd distribution also includes support for files with a.snmp extension that This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks implements a template-based Web interface to the Scotty SNMP Tcl extension. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] HTML + Tcl Templates The template system uses HTML pages that embed Tcl commands and Tcl variable references. The server replaces these using the subst command and returns the results. The server comes with a general template system, but using subst is so easy you could create your own template system. The TclHttpd template framework has these components: Each page.html can have a corresponding page.tml template file. This feature is enabled with theDoc_CheckTemplates command in the server's configuration file. Normally, the server returns the page.html file unless the corresponding page.tml file has been modified more recently. In this case, the server processes the template with subst, caches the result in thepage.html file, and returns the result. A dynamic template (e.g., a form handler) must be processed each time it is requested. If you put the Doc_Dynamic command into your page, it turns off the caching of the result in the page.html page. The server responds to a request for apage.html page by processing the page.tml page. Or you can just reference the page.tml file directly and the server will always processes the template. The server creates a page global Tcl variable that has context about the page being processed.Table 18-6 lists the elements of the page array. The server initializes the env global Tcl variable with similar information, but in the standard way for CGI scripts.Table 18-7 lists the elements of the env array that are set byCgi_SetEnv in cgi.tcl. The server initializes the ncgi module so you can use thencgi procedures to access query data. The server supports per-directory .tml files that contain Tcl source code. These files are designed to contain procedure definitions and variable settings that are shared among pages. The name of the file is simply ".tml", with nothing before the period. This is a standard way to hide files in UNIX, but it can be confusing to talk about the per-directory .tml files and the page.tml templates that correspond to page.html pages. Before processing each page.tml file, the server will source the.tml files in all directories leading down to the directory containing the template file. The server compares the modify time of these files against the template file and will process the template if these .tml files are newer than the cachedpage.html file. So, by modifying the.tml file in the root of your URL hierarchy, you invalidate all the cached page.html files. Where to Put Your Tcl Code There are three places you can put the code of your application: directly in your template pages, in the per-directory .tml files, or in the library directory. There are pros and cons to each: The library directory is where you should put most of your code. The library directory is specified with the -library command line argument, and the server loads all files in the library upon startup. The advantage of putting procedure definitions in the library is This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks that they are defined one time but executed many times. This works well with the Tcl byte-code compiler. The disadvantage is that if you modify procedures in these files, you have to explicitly source them into the server for these changes to take effect. You can restart the server, or you can use the /debug/source URL described on page 282 to reload source files into the running server. The .tml files are best for variable definitions that you want to share among pages in a directory, or as a staging area for procedures during development. The advantage of putting code into the per-directory .tml files is that changes are picked up immediately with no effort on your part. The server automatically checks if these files are modified and sources them each time it processes your templates. However, using .tml files tends to scatter your code around the URL tree and can make it harder to maintain. I try to put as little code as possible directly in my page.tml template files. It is awkward to put lots of code there, and you cannot share procedures and variable definitions easily with other pages. Instead, my goal is to have only procedure calls in the template files, and put the procedure definitions elsewhere. If you want control structures in your page, such as if and foreach, you may want to use the version of those commands provided by the html package, as described on page 277. Templates for Site Structure The next few examples show a simple template system used to maintain a common "look and feel" across the pages of a site. The key to a successful template system is a data structure that defines the structure of the site, and some procedures that generate standard navigational HTML structure for your pages. Once you do this, then you can easily add new pages by updating your data structure. The template procedures automatically reformat your site to include the new pages. Example 18-6 shows a simple one-level site definition that is kept in the root .tml file. This structure lists the title and URL of each page in the site: Example 18-6 A one-level site structure set site(pages) { Home /index.html "Ordering Computers"/ordering.html "New Machine Setup" /setup.html "Adding a New User" /newuser.html "Network Addresses" /network.html } Of course, your Web site is likely to have more pages and a more elaborate structure. For example, you might have several main sections, each with a collection of pages, or even a three-level hierarchy of pages. Example 18-7 shows another simple data structure to define a two-level structure. The site(sections) variable stores the names and URLs of the main sections. For each section, there is an element ofsite that lists the pages in that section. Only the About section is shown in the example: Example 18-7 A two-level site structure set site(sections) { About /about Products /products Support /support } set site(About) { . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Company company.html Contacts contacts.html Directions directions.html } In practice, you may want to include more information in your data structure to help you generate HTML. For example, if you have graphics for the main sections, you may need to record their size. Whatever you need, collect it into your data structures and then generate the HTML from procedures. You can quickly give your whole site a face lift with new graphics by changing the template procedures that generate your pages. In contrast, if you hand-code all your pages, it can take months instead of days. Example 18-8 shows a sample template file for the one-level structure shown inExample 18-6. Each page includes two commands,SitePage and SiteFooter, that generate HTML for the navigational part of the page. Between these commands is regular HTML for the page content: Example 18-8 A HTML + Tcl template file [SitePage "New Machine Setup"] This page describes the steps to take when setting up a new computer in our environment. See [SiteLink "Ordering Computers"] for instructions on ordering machines. <ol> <li>Unpack and setup the machine. <li>Use the Network control panel to set the IP address and hostname. <!-- Several steps omitted --> <li>Reboot for the last time. </ol> [SiteFooter] The SitePage procedure takes the page title as an argument. It generates HTML to implement a standard navigational structure.Example 18-9 has a simple implementation of SitePage: Example 18-9 SitePage template procedure, version 1 proc SitePage {title} { global site set html "<html><head><title>$title</title></head>\n" append html "<body bgcolor=white text=black>\n" append html "<h1>$title</h1>\n" set sep "" foreach {label url} $site(pages) { append html $sep if {[string compare $label $title] == 0} { append html "$label" } else { append html "<a href='$url'>$label</a>" } set sep " | " } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks return $html } The foreach loop that computes the simple menu of links turns out to be useful in many places.Example 18-10 splits out the loop and uses it in a new version of SitePage along with the SiteFooter procedure. This version of the templates creates a left column for the navigation and a right column for the page content. The example also puts a few more visual elements (e.g., page background color) into the site array so you can easily maintain them: Example 18-10 SiteMenu and SiteFooter template procedures array set site { bg white fg black mainlogo /images/mainLogo.gif } proc SitePage {title} { global site set html "<html><head><title>$title</title></head>\n\ <body bgcolor=$site(bg) text=$site(fg)>\n\ <!-- Two Column Layout -->\n\ <table cellpadding=0>\n\ <tr><td>\n\ <!-- Left Column -->\n\ <img src='$site(mainlogo)'>\n\ <font size=+1>\n\ [SiteMenu <br> $site(pages)]\n\ </font>\n\ </td><td>\n\ <!-- Right Column -->\n\ <h1>$title</h1>\n\ <p>\n" return $html } proc SiteFooter {} { global site set html "<p><hr>\n\ <font size=-1>[SiteMenu | $site(pages)]</font>\n\ <!-- Close Right Column -->\n\ </td></tr></table>\n" return $html } proc SiteMenu {sep list} { global page set s "" set html "" foreach {label url} $list { if {[string compare $page(url) $url] == 0} { append html $s$label } else { append html "$s<a href='$url'>$label</a>" } set s $sep } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks return $html } There are many other applications for "macros" that make repetitive HTML coding chores easy. For example, take the SiteLink procedure call in Example 18-8. Instead of hand-coding the <A> tag with the link to /ordering.html, the page uses the SiteLink procedure to format the link with a consistent label for the link. Using the procedure also means that the page will automatically get updated if you change the URL associated with the ordering page by modifying site(pages). Example 18-11 shows SiteLink: Example 18-11 The SiteLink procedure proc SiteLink {label} { global site array set map $site(pages) if {[info exist map($label)]} { return "<a href='$map($label)'>$label</a>" } else { return $label } } Using Variables for Important Site Information Another useful feature of templates is the ability to embed variable references in your pages. Instead of hard coding the sales phone number, or the current product version number, or even the product name, you can put variables into your pages. For example, SiteLink and SitePage take a parameter that is the page title. Instead of hard coding your page titles, you could keep all of your page titles in an array, and use array references everywhere. That puts all the text in one place and makes it easy to change. The array definition would look something like this: array set title { Home Home Order "Ordering Computers" Setup "New Machine Setup" AddUser "Adding a New User" Network "Network Addresses" } And the calls to SitePage or SiteLink could be made like this: [SitePage $title(Order)] The .tml pages are a good place to define the variables because the definitions are shared by all pages in that directory, and in any subdirectories. Also, the definitions in the per-directory .tml override any definitions that come from the top-level.tml file at the root of your URL tree. Changing the definition of the variable in the .tml file immediately updates all the pages that share it. The main drawback to variable references is the clash with $ in pricing. If you put $10 into a page.tml file, it will raise an error (unless the variable 10 is defined). It turns out that you want to generate prices from some database anyway, so you should avoid hard coding prices into your pages anyway. It is much better to put [price T-shirt] or $price(T-shirt) into your page than $10, although if you must do that, just quote the $ with a backslash, \$10. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Form Handlers HTML forms and form-handling programs go together. The form is presented to the user on the client machine. The form handler runs on the server after the user fills out the form and presses the submit button. The form presents input widgets like radiobuttons, checkbuttons, selection lists, and text entry fields. Each of these widgets is assigned a name, and each widget gets a value based on the user's input. The form handler is a program that looks at the names and values from the form and computes the next page for the user to read. CGI is a standard way to hook external programs to Web servers for the purpose of processing form data. CGI has a special encoding for values so that they can be transported safely. The encoded data is either read from standard input or taken from the command line. The CGI program decodes the data, processes it, and writes a new HTML page on its standard output. Chapter 3 describes writing CGI scripts in Tcl. TclHttpd provides alternatives to CGI that are more efficient because they are built right into the server. This eliminates the overhead that comes from running an external program to compute the page. Another advantage is that the Web server can maintain state between client requests in Tcl variables. If you use CGI, you must use some sort of database or file storage to maintain information between requests. Application Direct Handlers The server comes with several built-in form handlers that you can use with little effort. The /mail/forminfo URL will package up the query data and mail it to you. You use form fields to set various mail headers, and the rest of the data is packaged up into a Tcl-readable mail message. Example 18-12 shows a form that uses this handler. Other built-in handlers are described starting at page 281. Example 18-12 Mail form results with /mail/forminfo <form action=/mail/forminfo method=post> <input type=hidden name=sendto value=mailreader@my.com> <input type=hidden name=subject value="Name and Address"> <table> <tr><td>Name</td><td><input name=name></td></tr> <tr><td>Address</td><td><input name=addr1></td></tr> <tr><td> </td><td><input name=addr2></td></tr> <tr><td>City</td><td><input name=city></td></tr> <tr><td>State</td><td><input name=state></td></tr> <tr><td>Zip/Postal</td><td><input name=zip></td></tr> <tr><td>Country</td><td><input name=country></td></tr> </table> </form> The mail message sent by /mail/forminfo is shown in Example 18-13. Example 18-13 Mail message sent by /mail/forminfo . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks To: mailreader@my.com Subject: Name and Address data { name {Joe Visitor} addr1 {Acme Company} addr2 {100 Main Street} city {Mountain View} state California zip 12345 country USA } The email message is designed to be easily processed by a Tcl program. You can use a mail processor like procmail to filter all mail with a given Subject or To field to a program for processing. It is easy to write a script that strips the headers, defines a data procedure, and uses eval to process the message body. Whenever you send data via email, if you format it with Tcl list structure, you can process it quite easily. The basic structure of such a mail reader procedure is shown in Example 18-14: Example 18-14 Processing mail sent by /mail/forminfo # Assume the mail message is on standard input set X [read stdin] # Strip off the mail headers, when end with a blank line if {[regsub {.*?\n\ndata} $X {data} X] != 1} { error "Malformed mail message" } proc data {fields} { foreach {name value} $fields { # Do something } } # Process the message. eval $X The raw eval in the mail handler is dangerous. It will be fine if the only source of email to that program is the/mail/forminfo URL handler. However, an attacker could send you an email that results in arbitrary Tcl commands being evaluated by your mail processor. The safe way to process the email is with a safe interpreter, which is described in Chapter 19. Example 18-15 adds just a few commands to create a safe interpreter for processing the incoming data. The data command is evaluated in the trusted interpreter by the alias mechanism. All other commands in the email are evaluated in the safe interpreter, and any malicious commands simply raise Tcl errors but cause no harm: Example 18-15 Processing mail sent by /mail/forminfo, Safe-Tcl version # Assume the mail message is on standard input set X [read stdin] # Strip off the mail headers, when end with a blank line if {[regsub {.*?\n\ndata} $X {data} X] != 1} { error "Malformed mail message" } proc data {fields} { . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks foreach {name value} $fields { # Do something } } # Create the safe interpreter set i [interp create -safe] # Link the data command in the safe interpreter to the # data procedure in this interpreter interp alias $i data {} data # Process the message in the safe interpreter interp eval $i $X Template Form Handlers The drawback of using Application Direct URL form handlers is that you must modify their Tcl implementation to change the resulting page. Another approach is to use templates for the result page that embed a command that handles the form data. The Mail_FormInfo procedure, for example, mails form data. It takes no arguments. Instead, it looks in the query data for sendto and subject values, and if they are present, it sends the rest of the data in an email. It returns an HTML comment that flags that mail was sent. When you use templates to process form data, you need to turn off result caching because the server must process the template each time the form is submitted. To turn off caching, embed the Doc_Dynamic command into your form handler pages, or set thepage(dynamic) variable to 1. Alternatively, you can simply post directly to the file.tml page instead of to the file.html page. Self-Posting Forms This section illustrates a self-posting form. This is a form on a page that posts the form data to back to the same page. The page embeds a Tcl command to check its own form data. Once the data is correct, the page triggers a redirect to the next page in the flow. This is a powerful technique that I use to create complex page flows using templates. Of course, you need to save the form data at each step. You can put the data in Tcl variables, use the data to control your application, or store it into a database. TclHttpd comes with a Session module, which is one way to manage this information. For details, you should scan the session.tcl file in the distribution. Example 18-16 shows the Form_Simple procedure that generates a simple self-checking form. Its arguments are a unique ID for the form, a description of the form fields, and the URL of the next page in the flow. The field description is a list with three elements for each field: a required flag, a form element name, and a label to display with the form element: Example 18-16 A self-checking form procedure proc Form_Simple {id fields nextpage} { global page if {![html::varEmpty formid]} { # Incoming form values, check them set check 1 . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } else { # First time through the page set check 0 } set html "<!-- Self-posting. Next page is $nextpage -->\n" append html "<form action=\"$page(url)\" method=post>\n" append html "<input type=hidden name=formid value=$id>\n" append html "<table border=1>\n" foreach {required key label} $fields { append html "<tr><td>" if {$check && $required && [html::varEmpty $key]} { lappend missing $label append html "<font color=red>*</font>" } append html "</td><td>$label</td>\n" append html "<td><input [html::formValue $key]></td>\n" append html "</tr>\n" } append html "</table>\n" if {$check} { if {![info exist missing]} { # No missing fields, so advance to the next page. # In practice, you must save the existing fields # at this point before redirecting to the next page. Doc_Redirect $nextpage } else { set msg "<font color=red>Please fill in " append msg [join $missing ", "] append msg "</font>" set html <p>$msg\n$html } } append html "<input type=submit>\n</form>\n" return $html } The Form_Simple procedure does two things at once: it computes the HTML form, and it also checks if the required fields are present. It uses some procedures from the html module to generate form elements that retain values from the previous page. If all the required fields are present, then it triggers a redirect by calling Doc_Redirect. Example 18-17 shows a page template that calls Form_Simple with the required field description: Example 18-17 A page with a self-checking form <html><head> <title>Name and Address Form</title> </head> <body bgcolor=white text=black> <h1>Name and Address</h1> Please enter your name and address. [Form_Simple nameaddr { 1 name "Name" This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks 1 addr1 "Address" 0 addr2" "Address" 1 city "City" 0 state "State" 1 zip "Zip Code" 0 country "Country" } nameok.html] </body></html> The html Package The Standard Tcl Library, tcllib, includes an html package that is designed to support page generation and self-posting forms. Thehtml package works in conjunction with the ncgi package, which was introduced inChapter 3. The Form_Simple procedure uses html::varEmpty to test if particular form values are present in the query data. For example, it tests to see whether the formid field is present so that the procedure knows whether or not to check for the rest of the fields. The html::formValue procedure is useful for constructing form elements on self-posting form pages. It returns: name="name" value="value" The value is the value of form element name based on incoming query data, or just the empty string if the query value forname is undefined. As a result, the form can post to itself and retain values from the previous version of the page. It is used like this: <input type=text [html::formValue name]> The html::checkValue and html::radioValue procedures are similar to html::formValue, but are designed for checkbuttons and radio buttons. The html::select procedure formats a selection list and highlights the selected values. The html package includes a versions of foreach and if that are designed for use in templates. These commands perform asubst on their body instead of evaluating it. This lets you put HTML with variable and command references into the body to build up results. Example 18-18 shows the html::foreach procedure used to generate a table with several rows. Note that you don't have to worry about the $ in the prices because they are inside the braces of the html::foreach value list: Example 18-18 Generating a table with html::foreach <TABLE BORDER=1> [html::foreach {product price} { T-Shirt $10.00 YoYo $7.50 Footbag $15.00 }{ <TR> <TD>$product</TD> <TD ALIGN=RIGHT><FONT FACE=courier>$price</FONT></TD> </TR> } </TABLE> [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Programming Reference This section summarizes many of the more useful functions defined by the server. These tables are not complete, however. You are encouraged to read through the code to learn more about the features offered by the server. A simple naming convention is used to distinguish procedures that are private to a file (e.g., HttpdEvent) and procedures that are meant to be used by other modules or by the main application (e.g., Httpd_Server). The underscore after the module prefix indicates that the procedure is public. This section does not detail the ncgi and html packages, which are quite useful to the TclHttpd programmer. There are doc files that come with tcllib, and you can find man pages for the tcllib packages in the www.tcl.tk manual section. Table 18-1 shows Httpd functions used when returning pages to the client. Table 18-1. Httpd support procedures Httpd_Error sock code Returns a simple error page to the client. The code is a numeric error code such as 404 or 500. Httpd_ReturnData sock type data Returns a page with Content-Typetype and content data. Httpd_ReturnFile sock type file Returns a file with Content-Type type. Httpd_Redirect newurl sock Generates a 302 error return with a Location ofnewurl. Httpd_SelfUrl url Expands url to include the proper http://server:port prefix to reference the current server. Table 18-2 summarizes a few useful procedures provided by theUrl module (url.tcl). The Url_DecodeQuery is used to decode query data into a Tcl-friendly list. The Url_Encode procedure is useful when encoding values directly into URLs.URL encoding is discussed in more detail on page 262 Table 18-2. Url support procedures Url_DecodeQuery query Decodes a www-url-encoded query string and returns a name, value list.Depreciated. This is equivalent to ncgi::nvlist, which takes no arguments. Url_Encode value Returns value encoded according to the www-url-encoded standard. Url_PrefxInstall prefix handler Registers handler as the handler for all URLs that begin withprefix. The handler is invoked with two ?-thread bool? ?-callback cmd? ?-readpost bool? additional arguments: sock, the handle to the client, and suffix, the part of the URL after prefix. Use -thread 1 to have the handler run in a worker thread. Use-callback cmd to register a callback invoked at the very end of URL processing. Use -readpost 0 to disable pre-reading post data. The Doc module procedures for configuration are listed inTable 18-3. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 18-3. Doc procedures for configuration Doc_Root ?directory? Sets or queries the directory that corresponds to the root of the URL hierarchy. Doc_AddRoot virtual Maps the file systemdirectory into the URL subtree starting at virtual. directory Doc_ErrorPage file Specifies a file relative to the document root used as a simple template for error messages. This is processed by DocSubstSystem file in doc.tcl. Doc_CheckTemplates how If how is 1, then .html files are compared against corresponding.tml files and regenerated, if necessary. Doc_IndexFile pattern Registers a file name pattern that will be searched for the default index file in directories. Doc_NotFoundPage file Specifies a file relative to the document root used as a simple template for page not found messages. This is processed by DocSubstSystem file in doc.tcl. Doc_PublicHtml dirname Defines the directory used for each user's home directory. When a URL such as ~user is specified, the dirname under their home directory is accessed. Doc_TemplateLibrary Adds directory to the auto_path so that the source files in it are available to the server. directory Doc_TemplateInterp interp Specifies an alternate interpreter in which to process document templates (i.e.,.tml files.) Doc_Webmaster ?email? Sets or queries the email for the Webmaster. The Doc module procedures for generating results are listed inTable 18-4 Table 18-4. Doc procedures for generating responses Doc_Error sock errorInfo Generates a 500 response on sock based on the template registered with Doc_ErrorPage. errorInfo is a copy of the Tcl error trace after the error. Doc_NotFound sock Generates a 404 response onsock by using the template registered withDoc_NotFoundPage. Doc_Subst sock file Performs a subst on the file and return the resulting page onsock. interp specifies an alternate Tcl interpreter. ?interp? The Doc module also provides procedures for cookies and redirects that are useful in document templates. These are described in Table 18-5. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 18-5. Doc procedures that support template processing Doc_Coookie name Returns the cookie name passed to the server for this request, or the empty string if it is not present. Doc_Dynamic Turns off caching of the HTML result. Meant to be called from inside a page template. Doc_IsLinkToSelf url Returns 1 if the url is a link to the current page. Doc_Redirect newurl Raises a special error that aborts template processing and triggers a page redirect to newurl. Doc_SetCookie -name name -value value -path Sets cookie name with the given value that will be returned to the client as part of the path -domain domain -expires date response. The path and domain restrict the scope of the cooke. Thedate sets an expiration date. Table 18-6 shows the initial elements of the page array that are defined during the processing of a template. Table 18-6. Elements of the page array query The decoded query data in a name, value list. Also available throughncgi. dynamic If 1, the results of processing the template are not cached in the corresponding.html file. filename The file system pathname of the requested file (e.g.,/usr/local/htdocs/tclhttpd/index.html). template The file system pathname of the template file (e.g.,/usr/local/htdocs/tclhttpd/index.tml). url The part of the URL after the server name (e.g.,/tclhttpd/index.tml). root A relative path from the template file back to the root of the URL tree. Table 18-7 shows the elements of the env array. These are defined during CGI requests, Application Direct URL handlers, and page template processing: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 18-7. Elements of the env array AUTH_TYPE Authentication protocol (e.g., Basic). CONTENT_LENGTH The size of the query data. CONTENT_TYPE The type of the query data. DOCUMENT_ROOT File system pathname of the document root. GATEWAY_INTERFACE Protocol version, which is CGI/1.1. HTTP_ACCEPT The Accept headers from the request. HTTP_AUTHORIZATION The Authorization challenge from the request. HTTP_COOKIE The cookie from the request. HTTP_FROM The From: header of the request. HTTP_REFERER The Referer indicates the previous page. HTTP_USER_AGENT An ID string for the Web browser. PATH_INFO Extra path information after the template file. PATH_TRANSLATED The extra path information appended to the document root. QUERY_STRING The form query data. REMOTE_ADDR The client's IP address. REMOTE_USER The remote user name specified by Basic authentication. REQUEST_METHOD GET, POST, or HEAD. REQUEST_URI The complete URL that was requested. SCRIPT_NAME The name of the current file relative to the document root. SERVER_NAME The server name, e.g., www.beedub.com. SERVER_PORT The server's port, e.g., 80. SERVER_PROTOCOL The protocol (e.g., http or https). SERVER_SOFTWARE A software version string for the server. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Standard Application Direct URLs The server has several modules that provide Application Direct URLs. These Application Direct URLs let you control the server or examine its state from any Web browser. You can look at the implementation of these modules as examples for your own application. Status The /status URL is implemented in the status.tcl file. The status module implements the display of hit counts, document hits, and document misses (i.e., documents not found). The Status_Url command enables the Application Direct URLs and assigns the top-level URL for the status module. The default configuration file contains this command: Status_Url /status Table 18-8 shows the URLs implemented by the status module: Table 18-8. Status Application Direct URLs /status Main status page showing summary counters and hit count histograms. /status/doc Shows hit counts for each page. This page lets you sort by name or hit count, and limit files by patterns. /status/domain Shows hit counts for each domain in the server. /status/hello A trivial URL that returns "hello". /status/notfound Shows miss counts for URLs that users tried to fetch. /status/size Displays an estimated size of Tcl code and Tcl data used by the TclHttpd program. /status/text This is a version of the main status page that doesn't use the graphical histograms of hit counts. Debugging The /debug URL is implemented in the debug.tcl file. The debug module has several useful URLs that let you examine variable values and other internal state. It is turned on with this command in the default configuration file: Debug_Url /debug This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 18-9 lists the /debug URLs. These URLs often require parameters that you can specify directly in the URL. For example, the/debug/echo URL echoes its query parameters: http://yourserver:port/debug/echo?name=value&name2=val2 Note: The debug URL is active in the default configuration. If it makes you nervous, then delete the call toDebug_Url from the httpdthread.tcl file. The sample URL tree that is included in the distribution includes the file htdocs/hacks.html. This file has several small forms that use the /debug URLs to examine variables and source files. It may seem dangerous to have these facilities, but I reason that because my source directories are under my control, it cannot hurt to reload any source files. In general, the library scripts contain only procedure definitions and no global code that might reset state inappropriately. In practice, the ability to tune (i.e., fix bugs) in the running server has proven useful to me on many occasions. It lets you evolve your application without restarting it! Table 18-9. Debug Application Direct URLs /debug/after Lists the outstanding after events. /debug/dbg Connects to TclPro Debugger. This takes a host and port parameter. You need to install prodebug.tcl from TclPro into the server's script library directory. /debug/echo Echoes its query parameters. Accepts a title parameter. /debug/errorInfo Displays the errorInfo variable along with the server's version number and Webmaster email. Acceptstitle and errorInfo arguments. /debug/parray Displays a global array variable. The name of the variable is specified with theaname parameter. /debug/pvalue A more general value display function. The name of the variable is specified with the aname parameter. This can be a variable name, an array name, or a pattern that matches several variable names. /debug/raise Raises an error (to test error handling). Any parameters become the error string. /debug/source Sources a file from either the server's main library directory or the Doc_TemplateLibrary directory. The file is specified with the source parameter. Example 18-19 shows the implementation of /debug/source. You can see that it limits the files to the main script library and to the script library associated with document templates. Example 18-19 The /debug/source Application Direct URL implementation proc Debug/source {source} { global Httpd Config errorInfo set source [file tail $source] set dirlist $Httpd(library) ;# TclHttpd implementation lappend dirlist $Config(lib) ;# Application custom code foreach dir $dirlist { set file [file join $dir $source] if {[file exists $file]} break } set error [catch {uplevel #0 [list source $file]} result] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set html "<title>Source $source</title>\n" if {$error} { append html "<H1>Error in $source</H1>\n" append html "<pre>$result<p>$errorInfo</pre>" } else { append html "<H1>Reloaded $source</H1>\n" append html "<pre>$result</pre>" } return $html } Sending Email The /mail URL is implemented in the mail.tcl file. The mail module implements various form handlers that email form data. Currently, it is UNIX-specific because it uses /usr/lib/sendmail to send the mail. It is turned on with this command in the default configuration file: Mail_Url /mail The Application Direct URLs shown in Table 18-10 are useful form handlers. You can specify them as theACTION parameter in your<FORM> tags. The mail module provides two Tcl procedures that are generally useful. The MailInner procedure is the one that sends mail. It is called like this: Table 18-10. Application Direct URLS that email form results /mail/bugreport Sends email with the errorInfo from a server error. It takes anemail parameter for the destination address and an errorInfo parameter. Any additional arguments get included into the message. /mail/forminfo Sends email containing form results. It requires these parameters: sendto for the destination address, subject for the mail subject, href and label for a link to display on the results page. Any additional arguments are formatted with the Tcl list command for easy processing by programs that read the mail. /mail/formdata This is an older form of /mail/forminfo that doesn't format the data into Tcl lists. It requires only theemail and subject parameters. The rest are formatted into the message body. MailInner sendto subject from type body The sendto and from arguments are email addresses. The type is the MIME type (e.g., text/plain or text/html) and appears in a Content-Type header. The body contains the mail message without any headers. The Mail_FormInfo procedure is designed for use in HTML+Tcl template files. It takes no arguments but instead looks in current query data for its parameters. It expects to find the same arguments as the /mail/forminfo direct URL. Using a template withMail_FormInfo gives you more control over the result page than posting directly to /mail/forminfo, and is illustrated in Example 18-12 on page 272. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The TclHttpd Distribution Get the TclHttpd distribution from the CD-ROM, or find it on the Internet at: ftp://ftp.tcl.tk/pub/tcl/httpd/ http://www.tcl.tk/software/tclhttpd/ http://www.sourceforge.net/projects/tclhttpd Quick Start Unpack the tar file or the zip file, and you can run the server from the httpd.tcl script in the bin directory. On UNIX: tclsh bin/httpd.tcl -port 80 This command will start the Web server on the standard port (80). On UNIX, you need to be root to run a server on this port. By default TclHttpd uses port 8015 instead. If you run it with the -help flag, it will tell you what command line options are available. If you usewish instead of tclsh, then a simple Tk user interface is displayed that shows how many hits the server is getting. On Windows, you can double-click the httpd.tcl script to start the server. It will usewish and display the user interface. Again it will start on port 8015. You will need to create a shortcut that passes the -port argument, or edit the associated configuration file to change this. Configuring the server is described later. Once you have the server running, you can connect to it from your Web browser. Use this URL if you are running on the default (nonstandard) port: http://hostname:8015/ If you are running without a network connection, you may need to specify 127.0.0.1 for the hostname. This is the "localhost" address and will bypass the network subsystem. http://127.0.0.1:8015/ Inside the Distribution The TclHttpd distribution is organized into the following directories: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks bin — This has sample start-up scripts and configuration files. Thehttpd.tcl script runs the server. Thetclhttpd.rc file is the standard configuration file. bin/mini — This has a few tiny versions of the server that provide a basic server in about 300 lines of code. Use these as a starting point by modifying the HttpdRespond procedure. bin/test — This has a number of test scripts, including thetorture.tcl file that can fetch many URLs at once from a server. certs — This has sample certificates you can use to test a secure server forhttps URLs. If you have your own server certificates, put the server.pem file here. config — This contains autoconf support used by C extensions you can build with the server. custom — This is where you put your own custom code. Files here are automatically loaded by the server on startup. This contains a few samples. doc — This has a UNIX-style manual page for how to run the server. htaccess — This has sample access control files. htdocs — This is a sample URL tree that demonstrates the features of the Web server. There is also some documentation there. One directory to note is htdocs/libtml, which is the standard place to put site-specific Tcl scripts used with the Tcl+HTML template facility. lib — This has all the Tcl sources. In general, each file provides a package. You will see thepackage require commands partly in bin/httpd.tcl and partly inbin/httpdthread.tcl. src — There are a few C source files for a some optional packages. These have been precompiled for some platforms, and you can find the compiled libraries under src/Solaris and src/Linux. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Server Configuration TclHttpd configures itself with two main steps: setting configuration parameters and loading packages. The configuration step uses a configuration file and command line arguments to set basic configuration parameters. The default configuration file is named tclhttpd.rc in the same directory as the start-up script (i.e., bin/tclhttpd.rc). Specify an alternate configuration file with the-config command line argument. You can override the configuration file with additional command line arguments, which are described in Table 18-11. The configuration values from the file and the command line are copied into the Config Tcl array. Package loading is split into two parts. The main bin/httpd.tcl script loads some core packages. The rest are loaded in thebin/httpdthread.tcl script. The reason for the split is to try to isolate the core of the server from application-specific functions. In addition, in the threaded version of the server, every thread loads and runs the bin/httpdthread.tcl script. You can specify an alternate package loading script with the-main command line argument. For example, to start the server for the document tree under /usr/local/htdocs and your own email address as Webmaster, you can execute this command to start the server: tclsh httpd.tcl -docRoot /usr/local/htdocs -webmaster welch If you are using the Tclkit version described in Chapter 22: tclkit tclhttpd.kit -docRoot /usr/local/htdocs -webmaster welch Alternatively, you can put these settings into a configuration file, and start the server with that configuration file: tclsh httpd.tcl -config mytclhttpd.rc Command Line Arguments There are several parameters you may need to set for a standard Web server. These are shown below in Table 18-11. The command line values are mapped into the Config array by the httpd.tcl startup script. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 18-11. Basic TclHttpd parameters Parameter Command Option Config Variable Port number. The default is 8015. -port number Config(port) Server name. The default is [info hostname]. -name name Config(name) IP address. The default is 0, for "any address". -ipaddr address Config(ipaddr) Directory of the root of the URL tree. The default is the htdocs directory. -docRoot directory Config(docRoot) User ID of the TclHttpd process. The default is 50. (UNIX only.) -uid uid Config(uid) Group ID of the TclHttpd process. The default is 100. (UNIX only.) -gid gid Config(gid) Webmaster email. The default is webmaster. -webmaster email Config(webmaster) Configuration file. The default is tclhttpd.rc. -config filename Config(file) Directory containing custom code. The server loads all files found in this directory. -library directory Config(library) Server Name and Port The name and port parameters define how your server is known to Web browsers. The URLs that access your server begin with: http://name:port/ If the port number is 80, you can leave out the port specification. The call that starts the server using these parameters is found in httpd.tcl as: Httpd_Server $Config(name) $Config(port) $Config(ipaddr) Specifying the IP address is necessary only if you have several network interfaces (or several IP addresses assigned to one network interface) and want the server to listen to requests on a particular network address. Otherwise, by default, the server accepts requests from any network interface. User and Group ID The user and group IDs are used on UNIX systems with the setuid and setgid system calls. This lets you start the server as root, which is necessary to listen on port 80, and then switch to a less privileged user account. If you use Tcl+HTML templates that cache the results in HTML files, then you need to pick an account that can write those files. Otherwise, you may want to pick a very unprivileged account. The setuid function is available through the TclX (Extended Tcl)id command, or through asetuid extension distributed with TclHttpd under the src directory. If either of these facilities is not available, then the attempt to change user ID gracefully fails. See the README file in the src This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks directory for instructions on compiling and installing the extensions found there. Webmaster Email The Webmaster email address is used for automatic error reporting in the case of server errors. This is defined in the configuration file with the following command: Doc_Webmaster $Config(webmaster) If you call Doc_Webmaster with no arguments, it returns the email address you previously defined. This is useful when generating pages that contain mailto: URLs with the Webmaster address. Document Root The document root is the directory that contains the static files, templates, CGI scripts, and so on that make up your Web site. By default, the httpd.tcl script uses the htdocs directory next to the directory containing httpd.tcl. It is worth noting the trick used to locate this directory: file join [file dirname [info script]] ../htdocs The info script command returns the full name of the http.tcl script, file dirname computes its directory, and file join finds the adjacent directory. The path ../htdocs works with file join on any platform. The default location of the configuration file is found in a similar way: file join [file dirname [info script]] tclhttpd.rc The configuration file initializes the document root with this call: Doc_Root $Config(docRoot) If you need to find out what the document root is, you can call Doc_Root with no arguments and it returns the directory of the document root. If you want to add additional document trees into your Web site, you can do that with a call like this in your configuration file: Doc_AddRoot directory urlprefix Other Document Settings The Doc_IndexFile command sets a pattern used to find the index file in a directory. The command used in the default configuration file is: Doc_IndexFile index.{htm,html,tml,subst} If you invent other file types with different file suffixes, you can alter this pattern to include them. This pattern will be used by the Tcl glob command. The Doc_PublicHtml command is used to define "home directories" on your HTML site. If the URL begins with~username, then the Web This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks server will look under the home directory of the user for a particular directory. The command in the default configuration file is: Doc_PublicHtml public_html For example, if my home directory is /home/welch, then the URL ~welch maps to the directory /home/welch/public_html. If there is no Doc_PublicHtml command, then this mapping does not occur. You can register two special pages that are used when the server encounters an error and when a user specifies an unknown URL. The default configuration file has these commands: Doc_ErrorPage error.html Doc_NotFoundPage notfound.html These files are treated like templates in that they are passed through subst in order to include the error information or the URL of the missing page. These are pretty crude templates compared to the templates described earlier. You can count only on the Doc and Httpd arrays being defined. Look at the Doc_SubstSystemFile in doc.tcl for the truth about how these files are processed. Document Templates The template mechanism has two main configuration options. The first specifies an additional library directory that contains your application-specific scripts. This lets you keep your application-specific files separate from the TclHttpd implementation. The command in the default configuration file specifies the libtml directory of the document tree: Doc_TemplateLibrary [file join $Config(docRoot) libtml] You can also specify an alternate Tcl interpreter in which to process the templates. The default is to use the main interpreter, which is named {} according to the conventions described in Chapter 19. Doc_TemplateInterp {} Log Files The server keeps standard format log files. The Log_SetFile command defines the base name of the log file. The default configuration file uses this command: Log_SetFile /tmp/log$Config(port)_ By default, the server rotates the log file each night at midnight. Each day's log file is suffixed with the current date (e.g., /tmp/logport_990218.) The error log, however, is not rotated, and all errors are accumulated in/tmp/logport_error. The log records are normally flushed every few minutes to eliminate an extra I/O operation on each HTTP transaction. You can set this period with Log_FlushMinutes. If minutes is 0, the log is flushed on every HTTP transaction. The default configuration file contains: Log_FlushMinutes 1 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks CGI Directories You can register a directory that contains CGI programs with the Cgi_Directory command. This command has the interesting effect of forcing all files in the directory to be executed as CGI scripts, so you cannot put normal HTML files there. The default configuration file contains: Cgi_Directory /cgi-bin This means that the cgi-bin directory under the document root is a CGI directory. If you supply another argument toCgi_Directory, then this is a file system directory that gets mapped into the URL defined by the first argument. You can also put CGI scripts into other directories and use the .cgi suffix to indicate that they should be executed as CGI scripts. The cgi.tcl file has some additional parameters that you can tune only by setting some elements of the Cgi Tcl array. See the comments in the beginning of that file for details. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks . [ Team LiB ] Chapter 19. Multiple Interpreters and Safe-Tcl This chapter describes how to create more than one Tcl interpreter in your application. A child interpreter can be made safe so that it can execute untrusted scripts without compromising your application or your computer. Command aliases, hidden commands, and shared I/O channels enable communication among interpreters. Tcl command described is: interp. Safe-Tcl was invented by Nathaniel Borenstein and Marshall Rose so that they could send Tcl scripts via email and have the recipient safely execute the script without worry of viruses or other attacks. Safe-Tcl works by removing dangerous commands like exec and open that would let an untrusted script damage the host computer. You can think of this restricted interpreter as a "padded cell" in which it is safe to execute untrusted scripts. To continue the analogy, if the untrusted code wants to do anything potentially unsafe, it must ask permission. This works by adding additional commands, or aliases, that are implemented by a different Tcl interpreter. For example, asafeopen command could be implemented by limiting file space to a temporary directory that is deleted when the untrusted code terminates. The key concept of Safe-Tcl is that there are two Tcl interpreters in the application, a trusted one and an untrusted (or "safe") one. The trusted interpreter can do anything, and it is used for the main application (e.g., the Web browser or email user interface). When the main application receives a message containing an untrusted script, it evaluates that script in the context of the untrusted interpreter. The restricted nature of the untrusted interpreter means that the application is safe from attack. This model is much like user mode and kernel mode in a multiuser operating system like UNIX or Windows/NT. In these systems, applications run in user mode and trap into the kernel to access resources like files and the network. The kernel implements access controls so that users cannot read and write each other's files, or hijack network services. In Safe-Tcl the application implements access controls for untrusted scripts. The dual interpreter model of Safe-Tcl has been generalized in Tcl 7.5 and made accessible to Tcl scripts. A Tcl script can create other interpreters, destroy them, create command aliases among them, share I/O channels among them, and evaluate scripts in them. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The interp Command The interp command is used to create and manipulate interpreters. The interpreter being created is called aslave, and the interpreter that creates it is called the master. The master has complete control over the slave. Theinterp command is summarized in Table 19-1. Table 19-1. The interp command interp aliases slave Lists aliases that are defined inslave. interp alias slave cmd1 Returns target command and arguments for the aliascmd1 in slave. interp alias slave cmd1 master cmd2 arg... Defines cmd1 in slave that is an alias to cmd2 in master with additional args. interp create ?-safe? slave Creates an interpreter named slave. interp delete slave Destroys interpreter slave. interp eval slave cmd args ... Evaluates cmd and args in slave. interp exists slave Returns 1 if slave is an interpreter, else 0. interp expose slave cmd Exposes hidden command cmd in slave. interp hide slave cmd Hides cmd from slave. interp hidden slave Returns the commands hidden from slave. interp invokehidden slave cmd arg ... Invokes hidden command cmd and args in slave. interp issafe slave Returns 1 if slave was created with -safe flag. interp marktrusted slave Clears the issafe property of slave. interp recursionlimit slave ?limit? Set or get the interpreter recursion limit forslave. (Tcl 8.4) interp share master file slave Shares the I/O descriptor named file in master with slave. interp slaves master Returns the list of slave interpreters of master. interp target slave cmd Returns the name of the interpreter that is the target of aliascmd in slave. interp transfer master file slave Transfers the I/O descriptor named file from master to slave. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Creating Interpreters Here is a simple example that creates an interpreter, evaluates a couple of commands in it, and then deletes the interpreter: Example 19-1 Creating and deleting an interpreter interp create foo => foo interp eval foo {set a 5} => 5 set sum [interp eval foo {expr {$a + $a}}] => 10 interp delete foo In Example 19-1 the interpreter is named foo. Two commands are evaluated in thefoo interpreter: set a 5 expr {$a + $a} Note that curly braces are used to protect the commands from any interpretation by the main interpreter. The variable a is defined in the foo interpreter and does not conflict with variables in the main interpreter. The set of variables and procedures in each interpreter is completely independent. The Interpreter Hierarchy A slave interpreter can itself create interpreters, resulting in a hierarchy. The next examples illustrates this, and it shows how the grandparent of an interpreter can reference the grandchild by name. The example uses interp slaves to query the existence of child interpreters. Example 19-2 Creating a hierarchy of interpreters interp create foo => foo interp eval foo {interp create bar} => bar This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks interp create {foo bar2} => foo bar2 interp slaves => foo interp slaves foo => bar bar2 interp delete bar => interpreter named "bar" not found interp delete {foo bar} The example creates foo, and then it creates two children offoo. The first one is created by foo with this command: interp eval foo {interp create bar} The second child is created by the main interpreter. In this case, the grandchild must be named by a two-element list to indicate that it is a child of a child. The same naming convention is used when the grandchild is deleted: interp create {foo bar2} interp delete {foo bar2} The interp slaves operation returns the names of child (i.e., slave) interpreters. The names are relative to their parent, so the slaves offoo are reported simply as bar and bar2. The name for the current interpreter is the empty list, or{}. This is useful in command aliases and file sharing described later. For security reasons, it is not possible to name the master interpreter from within the slave. The Interpreter Name as a Command After interpreter slave is created, a new command is available in the main interpreter, also calledslave, that operates on the child interpreter. The following two forms are equivalent most operations: slave operation args ... interp operation slave args ... For example, the following are equivalent commands: foo eval {set a 5} interp eval foo {set a 5} And so are these: foo issafe interp issafe foo However, the operations delete, exists, share, slaves, target, and transfer cannot be used with the per interpreter command. In particular, there is no foo delete operation; you must use interp delete foo. If you have a deep hierarchy of interpreters, the command corresponding to the slave is defined only in the parent. For example, if a master creates foo, and foo creates bar, then the master must operate onbar with the interp command. There is no "foo bar" command defined in the master. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Use list with interp eval The interp eval command treats its arguments like eval. If there are extra arguments, they are all concatenated together first. This can lose important structure, as described in Chapter 10. To be safe, use list to construct your commands. For example, to safely define a variable in the slave, you should do this: interp eval slave [list set var $value] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Safe Interpreters A child can be created either safe (i.e., untrusted) or fully functional. In the examples so far, the children have been trusted and fully functional; they have all the basic Tcl commands available to them. An interpreter is made safe by eliminating certain commands. Table 19-2 lists the commands removed from safe interpreters. As described later, these commands can be used by the master on behalf of the safe interpreter. To create a safe interpreter, use the -safe flag: interp create -safe untrusted Table 19-2. Commands hidden from safe interpreters cd Changes directory. exec Executes another program. exit Terminates the process. fconfigure Sets modes of an I/O stream. file Queries file attributes. glob Matches on file name patterns. load Dynamically loads object code. open Opens files and process pipelines. pwd Determines the current directory. socket Opens network sockets. source Loads scripts. A safe interpreter does not have commands to manipulate the file system and other programs (e.g., cd, open, and exec). This ensures that untrusted scripts cannot harm the host computer. The socket command is removed so that untrusted scripts cannot access the network. The exit, source, and load commands are removed so that an untrusted script cannot harm the hosting application. Note that commands like puts and gets are not removed. A safe interpreter can still do I/O, but it cannot create an I/O channel. We will show how to pass an I/O channel to a child interpreter on page 299. The initial state of a safe interpreter is very safe, but it is too limited. The only thing a safe interpreter can do is compute a string and return that value to the parent. By creating command aliases, a master can give a safe interpreter controlled access to resources. A security policy implements a set of command aliases that add controlled capabilities to a safe interpreter. We will show, for example, how to provide limited network and file system access to untrusted slaves. Tcl provides a framework to manage several security policies, which is described in Chapter 20. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Command Aliases A command alias is a command in one interpreter that is implemented by a command in another interpreter. The master interpreter installs command aliases in its slaves. The command to create an alias has the following general form: interp alias slave cmd1 target cmd2 ?arg arg ...? This creates cmd1 in slave that is an alias forcmd2 in target. When cmd1 is invoked in slave, cmd2 is invoked in target. The alias mechanism is transparent to the slave. Whatever cmd2 returns, the slave sees as the return value ofcmd1. If cmd2 raises an error, the error is propagated to the slave. Name the current interpreter with{}. If target is the current interpreter, name it with {}. The empty list is the way to name yourself as the interpreter. This is the most common case, although target can be a different slave. The slave and target can even be the same interpreter. The arguments to cmd1 are passed to cmd2, after any additional arguments to cmd2 that were specified when the alias was created. These hidden arguments provide a safe way to pass extra arguments to an alias. For example, it is quite common to pass the name of the slave to the alias. In Example 19-3, exit in the interpreter foo is an alias that is implemented in the current interpreter (i.e.,{}). When the slave executes exit, the master executes: interp delete foo Example 19-3 A command alias for exit interp create foo interp alias foo exit {} interp delete foo interp eval foo exit # Child foo is gone. Alias Introspection This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks You can query what aliases are defined for a child interpreter. The interp aliases command lists the aliases; the interp alias command can also return the value of an alias, and the interp target command tells you what interpreter implements an alias. These are illustrated in the following examples: Example 19-4 Querying aliases proc Interp_ListAliases {name out} { puts $out "Aliases for $name" foreach alias [interp aliases $name] { puts $out [format "%-20s => (%s) %s" $alias \ [interp target $name $alias] \ [interp alias $name $alias]] } } Example 19-4 generates output in a human readable format.Example 19-5 generates the aliases as Tcl commands that can be used to re-create them later: Example 19-5 Dumping aliases as Tcl commands proc Interp_DumpAliases {name out} { puts $out "# Aliases for $name" foreach alias [interp aliases $name] { puts $out [format "interp alias %s %s %s %s" \ $name $alias [list [interp target $name $alias]] \ [interp alias $name $alias]] } } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Hidden Commands The commands listed in Table 19-2 are hidden instead of being completely removed. A hidden command can be invoked in a slave by its master. For example, a master can load Tcl scripts into a slave by using its hidden source command: interp create -safe slave interp invokehidden slave source filename Without hidden commands, the master has to do a bit more work to achieve the same thing. It must open and read the file and eval the contents of the file in the slave. File operations are described in Chapter 9. interp create -safe slave set in [open filename] interp eval slave [read $in] close $in Hidden commands were added in Tcl 7.7 in order to better support the Tcl/Tk browser plug-in described in Chapter 20. In some cases, hidden commands are strictly necessary; it is not possible to simulate them any other way. The best examples are in the context of Safe-Tk, where the master creates widgets or does potentially dangerous things on behalf of the slave. These will be discussed in more detail later. A master can hide and expose commands using the interp hide and interp expose operations, respectively. You can even hide Tcl procedures. However, the commands inside the procedure run with the same privilege as that of the slave. For example, if you are really paranoid, you might not want an untrusted interpreter to read the clock or get timing information. You can hide the clock and time commands: interp create -safe slave interp hide slave clock interp hide slave time You can remove commands from the slave entirely like this: interp eval slave [list rename clock {}] interp eval slave [list rename time {}] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Substitutions You must be aware of Tcl parsing and substitutions when commands are invoked in other interpreters. There are three cases corresponding to interp eval, interp invokehidden, and command aliases. With interp eval the command is subject to a complete round of parsing and substitutions in the target interpreter. This occurs after the parsing and substitutions for the interp eval command itself. In addition, if you pass several arguments to interp eval, those are concatenated before evaluation. This is similar to the way the eval command works as described in Chapter 19. The most reliable way to use interp eval is to construct a list to ensure the command is well structured: interp eval slave [list cmd arg1 arg2] With hidden commands, the command and arguments are taken directly from the arguments to interp invokehidden, and there are no substitutions done in the target interpreter. This means that the master has complete control over the command structure, and nothing funny can happen in the other interpreter. For this reason you should not create a list. If you do that, the whole list will be interpreted as the command name! Instead, just pass separate arguments to interp invokehidden and they are passed straight through to the target: interp invokehidden slave command arg1 arg2 Never eval alias arguments. With aliases, all the parsing and substitutions occur in the slave before the alias is invoked in the master. The alias implementation should never eval or subst any values it gets from the slave to avoid executing arbitrary code. For example, suppose there is an alias to open files. The alias does some checking and then invokes the hidden open command. An untrusted script might pass [exit] as the name of the file to open in order to create mischief. The untrusted code is hoping that the master will accidentally eval the filename and cause the application to exit. This attack has nothing to do with opening files; it just hopes for a poor alias implementation. Example 19-6 shows an alias that is not subject to this attack: Example 19-6 Substitutions and hidden commands interp alias slave open {} safeopen slave proc safeopen {slave filename {mode r}} { # do some checks, then... interp invokehidden $slave open $filename $mode } interp eval slave {open \[exit\]} This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The command in the slave starts out as: open \[exit\] The master has to quote the brackets in its interp eval command or else the slave will try to invoke exit because of command substitution. Presumably exit isn't defined, or it is defined to terminate the slave. Once this quoting is done, the value offilename is [exit] and it is not subject to substitutions. It is safe to use $filename in the interp invokehidden command because it is only substituted once, in the master. The hidden open command also gets [exit] as its filename argument, which is never evaluated as a Tcl command. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] I/O from Safe Interpreters A safe child interpreter cannot open files or network sockets directly. An alias can create an I/O channel (i.e., open a file or socket) and give the child access to it. The parent can share the I/O channel with the child, or it can transfer the I/O channel to the child. If the channel is shared, both the parent and the child can use it. If the channel is transferred, the parent no longer has access to the channel. In general, transferring an I/O channel is simpler, but sharing an I/O channel gives the parent more control over an unsafe child. The differences are illustrated in Example 19-7 and Example 19-9. There are three properties of I/O channels that are important to consider when choosing between sharing and transferring: the name, the seek offset, and the reference count. The name of the I/O channel (e.g., file4) is the same in all interpreters. If a parent transfers a channel to a child, it can close the channel by evaluating a close command in the child. Although names are shared, an interpreter cannot attempt I/O on a channel to which it has not been given access. The seek offset of the I/O channel is shared by all interpreters that share the I/O channel. An I/O operation on the channel updates the seek offset for all interpreters that share the channel. This means that if two interpreters share an I/O channel, their output will be cleanly interleaved in the channel. If they both read from the I/O channel, they will get different data. Seek offsets are explained in more detail on page 121. A channel has a reference count of all interpreters that share the I/O channel. The channel remains open until all references are closed. When a parent transfers an I/O channel, the reference count stays the same. When a parent shares an I/O channel, the reference count increments by one. When an interpreter closes a channel with close, the reference count is decremented by one. When an interpreter is deleted, all of its references to I/O channels are removed. The syntax of commands to share or transfer an I/O channel is: interp share interp1 chanName interp2 interp transfer interp1 chanName interp2 In these commands, chanName exists in interp1 and is being shared or transferred tointerp2. As with command aliases, ifinterp1 is the current interpreter, name it with {}. The following example creates a temporary file for an unsafe interpreter. The file is opened for reading and writing, and the slave can use it to store data temporarily. Example 19-7 Opening a file for an unsafe interpreter proc TempfileAlias {slave} { set i 0 while {[file exists Temp$slave$i]} { incr i } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks set out [open Temp$slave$i w+] interp transfer {} $out $slave return $out } proc TempfileExitAlias {slave} { foreach file [glob -nocomplain Temp$slave*] { file delete -force $file } interp delete $slave } interp create -safe foo interp alias foo Tempfile {} TempfileAlias foo interp alias foo exit {} TempfileExitAlias foo The TempfileAlias procedure is invoked in the parent when the child interpreter invokesTempfile. TempfileAlias returns the name of the open channel, which becomes the return value from Tempfile. TempfileAlias uses interp transfer to pass the I/O channel to the child so that the child has permission to access the I/O channel. In this example, it would also work to invoke the hidden open command to create the I/O channel directly in the slave. Example 19-7 is not fully safe because the unsafe interpreter can still overflow the disk or create a million files. Because the parent has transferred the I/O channel to the child, it cannot easily monitor the I/O activity by the child. Example 19-9 addresses these issues. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The Safe Base An safe interpreter created with interp create -safe has no script library environment and no way to source scripts. Tcl provides asafe base that extends a raw safe interpreter with the ability to source scripts and packages which are described in Chapter 12. The safe base also defines an exit alias that terminates the slave like the one inExample 19-7. The safe base is implemented as Tcl scripts that are part of the standard Tcl script library. Create an interpreter that uses the safe base with safe::interpCreate: safe::interpCreate foo The safe base has source and load aliases that only access directories on an access path defined by the master interpreter. The master has complete control over what files can be loaded into a slave. In general, it would be all right to source any Tcl program into an untrusted interpreter. However, untrusted scripts might learn things from the error messages they get by sourcing arbitrary files. The safe base also has versions of the package and unknown commands that support the library facility.Table 19-3 lists the Tcl procedures in the safe base: Table 19-3. The safe base master interface safe::interpCreate ?slave? ?options? Creates a safe interpreter and initialize the security policy mechanism. safe::interpInit slave ?options? Initializes a safe interpreter so it can use security policies. safe::interpConfigure slave ?options? Options are -accessPath pathlist, -nostatics, -deleteHook script, -nestedLoadOk. safe::interpDelete slave Deletes a safe interpreter. safe::interpAddToAccessPath slave directory Adds a directory to the slave's access path. safe::interpFindInAccessPath Maps from a directory to the token visible in the slave for that directory. safe::setLogCmd ?cmd arg ... ? Sets or queries the logging command used by the safe base. Table 19-4 lists the aliases defined in a safe interpreter by the safe base. Table 19-4. The safe base slave aliases source Loads scripts from directories in the access path. load Loads binary extensions from the slaves access path. file Only the dirname, join, extension, root, tail, pathname, and split operations are allowed. exit Destroys the slave interpreter. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Security Policies A security policy defines what a safe interpreter can do. Designing security policies that are secure is difficult. If you design your own, make sure to have your colleagues review the code. Give out prizes to folks who can break your policy. Good policy implementations are proven with lots of review and trial attacks. The good news is that Safe-Tcl security policies can be implemented in relatively small amounts of Tcl code. This makes them easier to analyze and get correct. Here are a number of rules of thumb: Small policies are better than big, complex policies. If you do a lot of complex processing to allow or disallow access to resources, chances are there are holes in your policy. Keep it simple. Never eval arguments to aliases. If an alias accepts arguments that are passed by the slave, you must avoid being tricked into executing arbitrary Tcl code. The primary way to avoid this is never to eval arguments that are passed into an alias. Watch your expressions, too. The expr command does an extra round of substitutions, so brace all your expressions so that an attacker cannot pass [exit] where you expect a number! Security policies do not compose. Each time you add a new alias to a security policy, it changes the nature of the policy. Even if alias1 and alias2 are safe in isolation, there is no guarantee that they cannot be used together to mount an attack. Each addition to a security policy requires careful review. Limited Socket Access The Safesock security policy provides limited socket access. The policy is designed around a simple table of allowed hosts and ports. An untrusted interpreter can connect only to addresses listed in the table. For example, I would never let untrusted code connect to the sendmail, ftp, or telnet ports on my hosts. There are just too many attacks possible on these ports. On the other hand, I might want to let untrusted code fetch a URL from certain hosts, or connect to a database server for an intranet application. The goal of this policy is to have a simple way to specify exactly what hosts and ports a slave can access. Example 19-8 shows a simplified version of the Safesock security policy that is distributed with Tcl 8.0. Example 19-8 The Safesock security policy # The index is a host name, and the # value is a list of port specifications, which can be # an exact port number # a lower bound on port number: N# a range of port numbers, inclusive: N-M This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks array set safesock { sage.eng 3000-4000 www.sun.com 80 webcache.eng {80 8080} bisque.eng {80 1025-} } proc Safesock_PolicyInit {slave} { interp alias $slave socket {} SafesockAlias $slave } proc SafesockAlias {slave host port} { global safesock if ![info exists safesock($host)] { error "unknown host: $host" } foreach portspec $safesock($host) { set low [set high ""] if {[regexp {^([0-9]+)-([0-9]*)$} $portspec x low high]} { if {($low <= $port && $high == "") || ($low <= $port && $high >= $port)} { set good $port break } } elseif {$port == $portspec} { set good $port } } if [info exists good] { set sock [interp invokehidden $slave socket $host $good] interp invokehidden $slave fconfigure $sock \ -blocking 0 return $sock } error "bad port: $port" } The policy is initialized with Safesock_PolicyInit. The name of this procedure follows a naming convention used by the safe base. In this case, a single alias is installed. The alias gives the slave a socket command that is implemented by SafesockAlias in the master. The alias checks for a port that matches one of the port specifications for the host. If a match is found, then the invokehidden operation is used to invoke two commands in the slave. The socket command creates the network connection, and thefconfigure command puts the socket into nonblocking mode so that read and gets by the slave do not block the application: set sock [interp invokehidden $slave socket $host $good] interp invokehidden $slave fconfigure $sock -blocking 0 The socket alias in the slave does not conflict with the hiddensocket command. There are two distinct sets of commands, hidden and exposed. It is quite common for the alias implementation to invoke the hidden command after various permission checks are made. The Tcl Web browser plug-in ships with a slightly improved version of the Safesock policy. It adds an alias forfconfigure so that the http package can set end of line translations and buffering modes. The fconfigure alias does not let you change the blocking behavior of the socket. The policy has also been extended to classify hosts into trusted and untrusted hosts based on their address. A different table of allowed ports is used for the two classes of hosts. The classification is done with two tables: One table lists patterns that match trusted hosts, and the other table lists hosts that should not be trusted even though they match the first table. The improved version also lets a downloaded script connect to the Web server that it came from. The Web browser plug-in is described in Chapter 20. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Limited Temporary Files Example 19-9 improves on Example 19-7 by limiting the number of temporary files and the size of the files. It is written to work with the safe base, so it has a Tempfile_PolicyInit that takes the name of the slave as an argument.TempfileOpenAlias lets the child specify a file by name, yet it limits the files to a single directory. The example demonstrates a shared I/O channel that gives the master control over output. TempfilePutsAlias restricts the amount of data that can be written to a file. By sharing the I/O channel for the temporary file, the slave can use commands like gets, eof, and close, while the master does the puts. The need for shared I/O channels is somewhat reduced by hidden commands, which were added to Safe-Tcl more recently than shared I/O channels. For example, the puts alias can either write to a shared channel after checking the file size, or it can invoke the hidden puts in the slave. This alternative is shown inExample 19-10. Example 19-9 The Tempfile security policy # Policy parameters: # directory is the location for the files # maxfile is the number of files allowed in the directory # maxsize is the max size for any single file. array set tempfile { maxfile 4 maxsize 65536 } # tempfile(directory) is computed dynamically based on # the source of the script proc Tempfile_PolicyInit {slave} { global tempfile interp alias $slave open {} \ TempfileOpenAlias $slave $tempfile(directory) \ $tempfile(maxfile) interp alias $slave puts {} TempfilePutsAlias $slave \ $tempfile(maxsize) interp alias $slave exit {} TempfileExitAlias $slave } proc TempfileOpenAlias {slave dir maxfile name {m r} {p 0777}} { global tempfile # remove sneaky characters regsub -all {|/:} [file tail $name] {} real set real [file join $dir $real] # Limit the number of files set files [glob -nocomplain [file join $dir *]] set N [llength $files] if {($N >= $maxfile) && (\ [lsearch -exact $files $real] < 0)} { error "permission denied" This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks } if [catch {open $real $m $p} out] { return -code error "$name: permission denied" } lappend tempfile(channels,$slave) $out interp share {} $out $slave return $out } proc TempfileExitAlias {slave} { global tempfile interp delete $slave if [info exists tempfile(channels,$slave)] { foreach out $tempfile(channels,$slave) { catch {close $out} } unset tempfile(channels,$slave) } } # See also the puts alias in Example 24–4 on page 389 proc TempfilePutsAlias {slave max chan args} { # max is the file size limit, in bytes # chan is the I/O channel # args is either a single string argument, # or the -nonewline flag plus the string. if {[llength $args] > 2} { error "invalid arguments" } if {[llength $args] == 2} { if {![string match -n* [lindex $argv 0]]} { error "invalid arguments" } set string [lindex $args 1] } else { set string [lindex $args 0]\n } set size [expr [tell $chan] + [string length $string]] if {$size > $max} { error "File size exceeded" } else { puts -nonewline $chan $string } } The TempfileAlias procedure is generalized in Example 19-9 to have parameters that specify the directory, name, and a limit to the number of files allowed. The directory and maxfile limit are part of the alias definition. Their existence is transparent to the slave. The slave specifies only the name and access mode (i.e., for reading or writing.) The Tempfile policy can be used by different slave interpreters with different parameters. The master is careful to restrict the files to the specified directory. It uses file tail to strip off any leading pathname components that the slave might specify. The tempfile(directory) definition is not shown in the example. The application must choose a directory when it creates the safe interpreter. The Browser security policy described on page 317 chooses a directory based on the name of the URL containing the untrusted script. The TempfilePutsAlias procedure implements a limited form of puts. It checks the size of the file withtell and measures the output string to see if the total exceeds the limit. The limit comes from a parameter defined when the alias is created. The file cannot grow past the limit, at least not by any action of the child interpreter. The args parameter is used to allow an optional-nonewline flag to puts. The value of args is checked This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks explicitly instead of using the eval trick described in Example 10-3 on page 136. Never eval arguments to aliases or else a slave can attack you with arguments that contain embedded Tcl commands. The master and slave share the I/O channel. The name of the I/O channel is recorded in tempfile, and TempfileExitAlias uses this information to close the channel when the child interpreter is deleted. This is necessary because both parent and child have a reference to the channel when it is shared. The child's reference is automatically removed when the interpreter is deleted, but the parent must close its own reference. The shared I/O channel lets the master use puts and tell. It is also possible to implement this policy by using hiddenputs and tell commands. The reason tell must be hidden is to prevent the slave from implementing its own version oftell that lies about the seek offset value. One advantage of using hidden commands is that there is no need to clean up the tempfile state about open channels. You can also layer the puts alias on top of any existing puts implementation. For example, a script may define puts to be a procedure that inserts data into a text widget. Example 19-10 shows the difference when using hidden commands. Example 19-10 Restricted puts using hidden commands proc Tempfile_PolicyInit {slave} { global tempfile interp alias $slave open {} \ TempfileOpenAlias $slave $tempfile(directory) \ $tempfile(maxfile) interp hide $slave tell interp alias $slave tell {} TempfileTellAlias $slave interp hide $slave puts interp alias $slave puts {} TempfilePutsAlias $slave \ $tempfile(maxsize) # no special exit alias required } proc TempfileOpenAlias {slave dir maxfile name {m r} {p 0777}} { # remove sneaky characters regsub -all {|/:} [file tail $name] {} real set real [file join $dir $real] # Limit the number of files set files [glob -nocomplain [file join $dir *]] set N [llength $files] if {($N >= $maxfile) && (\ [lsearch -exact $files $real] < 0)} { error "permission denied" } if [catch {interp invokehidden $slave \ open $real $m $p} out] { return -code error "$name: permission denied" } return $out } proc TempfileTellAlias {slave chan} { interp invokehidden $slave tell $chan } proc TempfilePutsAlias {slave max chan args} { if {[llength $args] > 2} { error "invalid arguments" } if {[llength $args] == 2} { if {![string match -n* [lindex $args 0]]} { This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks error "invalid arguments" } set string [lindex $args 1] } else { set string [lindex $args 0]\n } set size [interp invokehidden $slave tell $chan] incr size [string length $string] if {$size > $max} { error "File size exceeded" } else { interp invokehidden $slave \ puts -nonewline $chan $string } } Safe after Command The after command is unsafe because it can block the application for an arbitrary amount of time. This happens if you only specify a time but do not specify a command. In this case, Tcl just waits for the time period and processes no events. This will stop all interpreters, not just the one doing the after command. This is a kind of resource attack. It doesn't leak information or damage anything, but it disrupts the main application. Example 19-11 defines an alias that implements after on behalf of safe interpreters. The basic idea is to carefully check the arguments, and then do the after in the parent interpreter. As an additional feature, the number of outstandingafter events is limited. The master keeps a record of each after event scheduled. Two IDs are associated with each event: one chosen by the master (i.e., myid), and the other chosen by the after command (i.e., id). The master keeps a map frommyid to id. The map serves two purposes: The number of map entries counts the number of outstanding events. The map also hides the real after ID from the slave, which prevents a slave from attempting mischief by specifying invalid after IDs to after cancel. The SafeAfterCallback is the procedure scheduled. It maintains state and then invokes the original callback in the slave. Example 19-11 A safe after command # SafeAfter_PolicyInit creates a child with # a safe after command proc SafeAfter_PolicyInit {slave max} { # max limits the number of outstanding after events global after interp alias $slave after {} SafeAfterAlias $slave $max interp alias $slave exit {} SafeAfterExitAlias $slave # This is used to generate after IDs for the slave. set after(id,$slave) 0 } # SafeAfterAlias is an alias for after. It disallows after This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # with only a time argument and no command. proc SafeAfterAlias {slave max args} { global after set argc [llength $args] if {$argc == 0} { error "Usage: after option args" } switch -- [lindex $args 0] { cancel { # A naive implementation would just # eval after cancel $args # but something dangerous could be hiding in args. set myid [lindex $args 1] if {[info exists after(id,$slave,$myid)]} { set id $after(id,$slave,$myid) unset after(id,$slave,$myid) after cancel $id } return "" } default { if {$argc == 1} { error "Usage: after time command args..." } if {[llength [array names after id,$slave,*]]\ >= $max} { error "Too many after events" } # Maintain concat semantics set command [concat [lrange $args 1 end]] # Compute our own id to pass the callback. set myid after#[incr after(id,$slave)] set id [after [lindex $args 0] \ [list SafeAfterCallback $slave $myid $command]] set after(id,$slave,$myid) $id return $myid } } } # SafeAfterCallback is the after callback in the master. # It evaluates its command in the safe interpreter. proc SafeAfterCallback {slave myid cmd} { global after unset after(id,$slave,$myid) if [catch { interp eval $slave $cmd } err] { catch {interp eval $slave bgerror $error} } } # SafeAfterExitAlias is an alias for exit that does cleanup. proc SafeAfterExitAlias {slave} { global after This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks foreach id [array names after id,$slave,*] { after cancel $after($id) unset after($id) } interp delete $slave } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 20. Safe-Tk and the Browser Plugin This chapter describes Safe-Tk that lets untrusted scripts display and manipulate graphical user interfaces. The main application of Safe-Tk is the Tcl/Tk plugin for Web browsers like Netscape Navigator and Internet Explorer. Safe-Tk supports network applets that display user interfaces. The main vehicle for Safe-Tk is a plugin for Netscape Navigator, Mozilla and Internet Explorer. The plugin supports Tcl applets, or Tclets, that are downloaded from the Web server and execute inside a window in a Web browser. For the most part, Tcl/Tk applications can run unchanged in the plugin. However, security policies place some restrictions on Tclets. The plugin supports multiple security policies, so Tclets can do a variety of interesting things in a safe manner. You can configure the plugin to use an existing wish application to host the Tcl applets if you require a newer version of Tk, or the plugin can load the Tcl/Tk shared libraries and everything runs in the browser process. You can use a custom wish that has extensions built in or dynamically loaded. This gives intranet applications of the plugin the ability to access databases and other services that are not provided by the Tcl/Tk core. With the security policy mechanism you can still provide mediated access to these resources. This chapter describes how to set up the plugin. Jeff Hobbs recently updated the plugin to use Tcl/Tk 8.4. Compiled versions of the plugin are available as part of the Tcl Dev Kit from ActiveState. The source code of the plugin is freely available. You can recompile the plugin against newer versions of Tcl/Tk, or build custom plugins that have your own Tcl extensions built in. You can find its sources at: http://tclplugin.sourceforge.net/ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Tk in Child Interpreters A child interpreter starts out with just the core Tcl commands. It does not include Tk or any other extensions that might be available to the parent interpreter. This is true whether or not the child interpreter is declared safe. You add extensions to child interpreters by using a form of the load command that specifies an interpreter: load {} Tk child Normally, load takes the name of the library file that contains the extension. In this case, the Tk package is astatic package that is already linked into the program (e.g., wish or the plugin), so the file name is the empty string. Theload command calls the Tk initialization procedure to register all the Tcl commands provided by Tk. Embedding Tk Windows By default, a slave interpreter that loads Tk gets a new top-level window. Wish supports a -use command line option that directs Tk to use an existing window as dot. You can use this to embed an application within another. For example, the following commands run a copy of Wish that uses the .embed toplevel as its main window: toplevel .embed exec wish -use [winfo id .embed] somescript.tcl & More often, embedding is used with child interpreters. If the interpreter is not safe, you can set the argv and argc variables in the slave before loading Tk: interp create trustedTk interp eval trustedTk \ [list set argv [list -use [winfo id .embed]]] interp eval trustedTk [list set argc 2] load {} Tk trustedTk If the child interpreter is safe, then you cannot set argv and argc directly. The easiest way to pass-use to a safe interpreter is with the safe::loadTk command: safe::interpCreate safeTk safe::loadTk safeTk -use [winfo id .embed] When Tk is loaded into a safe interpreter, it calls back into the master interpreter and evaluates the safe::TkInit procedure. The job of this procedure is to return the appropriate argv value for the slave. The safe::loadTk procedure stores its additional arguments in the safe::tkInit variable, and this value is retrieved by the safe::TkInit procedure and returned to the slave. This protocol is used so that a safe interpreter cannot attempt to hijack the windows of its master by constructing its own argv variable! This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Safe-Tk Restrictions When Tk is loaded into a safe interpreter, it hides several Tk commands. Primarily these are hidden to prevent denial of service attacks against the main process. For example, if a child interpreter did a global grab and never released it, all input would be forever directed to the child. Table 20-1 lists the Tk commands hidden by default from a safe interpreter. The Tcl commands that are hidden insafe interpreters are listed on page 295. Table 20-1. Tk commands omitted from safe interpreters bell Rings the terminal bell. clipboard Accesses the CLIPBOARD selection. grab Directs input to a specified widget. menu Creates and manipulates menus, because menus needgrab. selection Manipulates the selection. send Executes a command in another Tk application. tk appname Sets the application name. tk_chooseColor Color choice dialog. tk_chooseDirectory Directory chooser dialog. tk_getOpenFile File open dialog. tk_getSaveFile File save dialog. tk_messageBox Simple dialog boxes. toplevel Creates a detached window. wm Controls the window manager. If you find these restrictions limiting, you can restore commands to safe interpreters with the interp expose command. For example, to get menus and toplevels working, you could do: interp create -safe safeTk foreach cmd {grab menu menubutton toplevel wm} { interp expose safeTk $cmd } Instead of exposing the command directly, you can also construct aliases that provide a subset of the features. For example, you could disable the -global option to grab. Aliases are described in detail inChapter 19. The Browser plugin defines a more elaborate configuration system to control what commands are available to slave interpreters. You can have lots of control, but you need to distribute the security policies that define what Tclets can do in the plugin. Configuring security policies This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks for the plugin is described later. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The Browser Plugin The HTML EMBED tag is used to put various objects into a Web page, including a Tcl program.Example 20-1 shows the EMBED tag used to insert a Tclet: Example 20-1 Using EMBED to insert a Tclet <EMBED TYPE="application/x-tcl" PLUGINSPAGE="http://www.tcl.tk/plugin/" WIDTH="400" HEIGHT="300" SRC="eval.tcl" </EMBED> The width and height are interpreted by the plugin as the size of the embedded window. Thesrc specifies the URL of the program. These parameter names (e.g., width) are case sensitive and should be lowercase. In the above example,eval.tcl is a relative URL, so it should be in the same directory as the HTML file that has the EMBED tag. The window size is fixed in the browser, which is different from normal toplevels in Tk. The plugin turns off geometry propagation on your main window so that your Tclet stays the size allocated. There are also "full window" Tclets that do not use an EMBED tag at all. Instead, you just specify the .tcl file directly in the URL. In this case, the plugin occupies the whole browser window and will resize as you resize the browser window. The embed_args and plugin Variables The parameters in the EMBED tag are available to the Tcl program in theembed_args variable, which is an array with the parameter names as the index values. For example, the string for a ticker-tape Tclet can be passed in the EMBED tag as the string parameter, and the Tclet will use $embed_args(string) as the value to display: <EMBED src=ticker.tcl width=400 height=50 string="Hello World"> Note that HTML tag parameters are case sensitive. Your Tclet may want to map all the parameter names to lowercase for convenience: foreach {name value} [array get embed_args] { set embed_args([string tolower $name]) $value } The plugin array has version, patchLevel, and release elements that identify the version and release date of the plugin implementation. Example Plugins This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The plugin home page is a great place to find Tclet examples. There are several plugins done by the Tcl/Tk team at Sunlabs, plus links to a wide variety of Tclets done on the Net. http://www.tcl.tk/plugin/ My first plugin was calculator for the effective wheel diameter of multigear bicycles. Brian Lewis, who built the Tcl 8.0 byte-code compiler, explained to me the concept and how important this information is to bicycle enthusiasts. The Tclet that displays the gear combinations on a Tk canvas and lets you change the number of gears and their size. You can find the result at: http://www.beedub.com/plugin/bike.html Setting Up the plugin There are plugin versions for UNIX, Windows, and Macintosh. The installation details vary somewhat between platforms and between releases of the plugin. The following components make up the plugin installation: The plugin shared libraries (i.e., DLLs). The Web browser dynamically loads the plugin implementation when it needs to execute a Tclet embedded in a Web page. There is a standard directory that the browser scans for the libraries that implement plugins. The Tcl/Tk script libraries. The plugin needs the standard script libraries that come with Tcl and Tk, plus it has its own scripts that complete its implementation. Each platform has a plugin script directory with these subdirectories: tcl, tk, plugin, config, safetcl, and utils. The plugin implementation is in theplugin directory. The security policies. These are kept in asafetcl directory that is a peer of the Tcl script library. The trust configuration. This defines what Tclets can use which security policies. This is in aconfig directory that is a peer of the Tcl script library. Local hooks. Local customization is supported by two hooks,siteInit and siteSafeInit. The siteInit procedure is called from the plugin when it first loads, and siteSafeInit is called when each applet is initialized. It is called with the name of the slave interpreter and the list of arguments from the <EMBED> tag. You can provide these as scripts that get loaded from theauto_path of the master interpreter. Chapter 12 describes how to manage script libraries found in theauto_path. The plugin also sources a personal start up script in which you can define siteInit and siteSafeInit. This script is ~/.pluginrc on UNIX and plugin/tclplugin.rc on Windows and Macintosh. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Security Policies and Browser Plugin Tclets run in a safe interpreter that is set up with the safe base facilities described on page 300. This limits a Tclet to a display-only application. To do something more interesting, you must grant the Tclet more privilege. The extra functions are bundled together into a security policy, which is implemented as a set of command aliases. Unlike a Java applet, a Tclet can choose from different security policies. A few standard security policies are distributed with the plugin, and these are described below. You can also create custom security policies to support intranet applications. You can even choose to grant certain Tclets the full power of Tcl/Tk. The policy command is used to request a security policy: policy name The policies that are part of the standard plugin distribution are described below. The home, inside, and outside policies all provide limited network access. They differ in what set of hosts are accessible. The default trust configuration lets any Tclet request the home, inside, or outside policy. home. This provides a socket and fconfigure commands that are limited to connecting to the host from which the Tclet was downloaded. You can specify an empty string for the host argument to socket to connect back to the home host. This policy also supports open and file delete that are similar to theTempfile policy shown in Example 19-9 on page 304. This provides limited local storage that is inside a directory that is, by default, private to the Tclet. Files in the private directory persist after the Tclet exits, so it can maintain long term state. Tclets from the same server can share the directory by putting the same prefix=partialurl argument in their EMBED tag. The partialurl must be a prefix of the Tclet's URL. Finally, thehome policy automatically provides a browser package that is described later. inside. This is just like thehome policy, except that the site administrator controls a table of hosts and ports to which untrusted slaves can connect with socket. A similar set of tables control what URLs can be accessed with thebrowser package. This is similar to the Safesock policy shown in Example 19-8 on page 302. The set of hosts is supposed to be inside the firewall. The local file storage used by this policy is distinct from that used by the home and outside policies. This is true even if Tclets try to share by using the prefix=partialurl parameter. outside. This is just like thehome and inside policies, except that the set of hosts is configured to be outside the firewall. The local file storage used by this policy is distinct from that used by the home and inside policies. trusted. This policy restores all features of Tcl and Tk. This policy lets you launch all your Tcl and Tk applications from the Web browser. The default trust map settings do not allow this for any Tclet. The trust map configuration is described later. javascript. This policy provides a superset of thebrowser package that lets you invoke arbitrary Javascript and to write HTML directly to frames. This does not have the limited socket or temporary file access that the home, inside, and outside policies have. However, the javascript policy places no restrictions on the URLs you can fetch, plus it lets Tclets execute Java-script, which may have its own security risks. The default trust map settings do not allow this for any Tclet. The Browser Package The browser package is bundled with several of the security policies. It makes many features of the Web browser accessible to Tclets. They can fetch URLs and display HTML in frames. However, the browser package has some risks associated with it. HTTP requests can be used to transmit information, so a Tclet using the policy could leak sensitive information if it can fetch a URL outside the firewall. To avoid This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks information leakage, the inside, outside, and home policies restrict the URL that can be fetched withbrowser::getURL. Table 20-2 lists the aliases defined by the browser package. Table 20-2. Aliases defined by the browser package browser::status string Displays string in the browser status window. browser::getURL url ?timeout? ?newcallback? Fetches url, if allowed by the security policy. Thecallbacks occur before, during, and ?writecallback? ?endcallback? after the url data is returned. browser::displayURL url frame Causes the browser to display url in frame. browser::getForm url data ?raw? ?timeout? Posts data to url. The callbacks are the same as for browser::getURL. If raw is 0, then ?newcallback? ?writecallback? ?endcallback? data is a name value list that gets encoded automatically. Otherwise, it is assumed to be encoded already. browser::displayForm url frame data ?raw? Posts data to url and displays the result inframe. The raw argument is the same as in browser::getForm. The browser::getURL function uses the browser's built-in functions, so it understands proxies and supportsftp:, http:, and file: urls. Unfortunately, the browser::getURL interface is different from the http::geturl interface. It uses a more complex callback scheme that is due to the nature of the browser's built-in functions. If you do not specify any callbacks, then the call blocks until all the data is received, and then that data is returned. The callback functions are described in Table 20-3. Table 20-3. The browser::getURL callbacks newcallback name stream url This is called when data starts to arrive from url. The name identifies the requesting Tclet, and the mimetype datemodified size stream identifies the connection. The mimetype, datemodified, and size parameters are attributes of the returned data. writecallback name stream size data This is called when size bytes of data arrive for Tcllet name over stream. endcallback name stream reason data This is called when the request has completed, although there may be some final bytes in data. The [ Team LiB ] reason is one of: EOF, NETWOR_ERROR, USER_BREAK, or TIMEOUT. . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Configuring Security Policies There are three aspects to the plugin security policy mechanism: policies, features, and trust maps. A policy is an umbrella for a set of features that are allowed for certain Tclets based on the trust map. A feature is a set of commands and aliases that are defined for a safe interpreter that requests a policy. The trust map is a filter based on the URL of the Tclet. In the future, trust may bet determined by digital signatures instead of URLs. The trust map determines whether a Tclet can request a given policy. Security Policies are configured for each client. Remember that the configuration files affect the client machine, which is the workstation that runs the Web browser. If you create Tclets that require custom security policies, you have the burden of distributing the configuration files to clients that will use your Tclets. You also have the burden of convincing them that your security policy is safe! The config/plugin.cfg File The main configuration file is the config/plugin.cfg file in the plugin distribution. This file lists what features are supported by the plugin, and it defines the URL filters for the trust map. The configuration file is defined into sections with a section command. The policies section defines which Tclets can use which security policies. For example, the default configuration file contains these lines in the policies section: section policies allow home disallow intercom disallow inside disallow outside disallow trusted allow javascript ifallowed trustedJavaScriptURLS \ $originURL This configuration grants all Tclets the right to use the home policy, disallows all Tclets from using theintercom, inside, outside, and trusted policies, and grants limited access to the javascript policy. If you are curious, the configuration files are almost Tcl, but not quite. I lost an argument about that one, so these are stylized configuration files that follow their own rules. For example, the originURL variable is not This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks defined in the configuration file but is a value that is tested later when the Tclet is loaded. I'll just give examples here and you can peer under the covers if you want to learn how they are parsed. The ifallowed clause depends on another section to describe the trust mapping for that policy. For thejavascript policy, the config/plugin.cfg file contains: section trustedJavascriptURLs allow http://sunscript.sun.com:80/plugin/javascript/* Unfortunately, this server isn't running anymore, so you may want to add the Scriptics Web server to your own configuration: allow http://www.tcl.tk:80/plugin/javascript/* You can use a combination of allow and disallow rules in a section. The arguments toallow and disallow are URL string match patterns, and they are processed in order. For example, you could put a liberal allow rule followed bydisallow rules that restrict access, or vice versa. It is probably safest to explicitly list each server that you trust. Policy Configuration Files Each security policy has a configuration file associated with it. For example, the outside policy uses the file outside.cfg file in the config directory. This file specifies what hosts and ports are accessible to Tclets using the outside policy. For the inside and outside policies, the configuration files are similar in spirit to the safesock array used to configure the Safesock security policy shown on page 302. There are a set of allowed hosts and ports, and a set of excluded hosts. The excluded hosts are an exception list. If a host matches the included set but also matches the excluded set, it is not accessible. There is an included and excluded set for URLs that affect browser::geturl. The settings from the Tempfile policy shown on page 304 are also part of thehome, inside, and outside configuration files. The configuration files are well commented, and you should read through them to learn about the configuration options for each security policy. Security Policy Features The aliases that make up a security policy are organized into sets called features. The features are listed in the mainconfig/plugin.cfg configuration file: variable featuresList {url stream network persist unsafe} In turn, each security policy configuration file lists what features are part of the policy. For example, the config/home.cfg file lists these features: section features allow url allow network allow persist unless {[string match {UNKNOWN *} \ [getattr originURL]]} Each feature is implemented in a file in the safetcl directory of the distribution. For example, the url feature is implemented insafetcl/url.tcl. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The code in these files follows some conventions in order to work with the configuration mechanism. Each one is implemented inside a namespace that is a child of the safefeature namespace (e.g., safefeature::url). It must implement an install procedure that is called to initialize the feature for a new Tclet. It is inside this procedure that the various allow/disallow rules are checked. The cfg::allowed command supports the rule language used in the .cfg files. Creating New Security Policies This book does not describe the details of the configuration language or the steps necessary to create a new security policy. There are several manual pages distributed with the plugin that explain these details. They can be found on the Web at: http://www.tcl.tk/plugin/man/ If you are serious about tuning the existing security policies or creating new ones, you should read the existing feature implementations in detail. As usual, modifying a working example is the best way to proceed! I think it is a very nice property of the plugin that its security policies are implemented in Tcl source code that is clearly factored out from the rest of the Tcl/Tk and plugin implementation. With a relatively small amount of code, you can create custom security policies that grant interesting abilities to Tclets. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 21. Multi-Threaded Tcl Scripts This chapter describes the Thread extension for creating multi-threaded Tcl scripts. Thread support, a key feature of many languages, is a recent addition to Tcl. That's because the Tcl event loop supports features implemented by threads in most other languages, such as graphical user interface management, multi-client servers, asynchronous communication, and scheduling and timing operations. However, although Tcl's event loop can replace the need for threads in many circumstances, there are still some instances where threads can be a better solution: Long-running calculations or other processing, which can "starve" the event loop Interaction with external libraries or processes that don't support asynchronous communication Parallel processing that doesn't adapt well to an event-driven model Embedding Tcl into an existing multi-threaded application [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] What are Threads? Traditionally, processes have been limited in that they can do only one thing at a time. If your application needed to perform multiple tasks in parallel, you designed the application to create multiple processes. However, this approach has its drawbacks. One is that processes are relatively "heavy" in terms of the resources they consume and the time it takes to create them. For applications that frequently create new processes — for example, servers that create a new process to handle each client connection — this can lead to decreased response time. And widely parallel applications that create many processes can consume so many system resources as to slow down the entire system. Another drawback is that passing information between processes can be slow because most interprocess communication mechanisms — such as files, pipes, and sockets — involve intermediaries such as the file system or operating system, as well as requiring a context switch from one running process to another. Threads were designed as a light-weight alternative. Threads are multiple flows of execution within the same process. All threads within a process share the same memory and other resources. As a result, creating a thread requires far fewer resources than creating a separate process. Furthermore, sharing information between threads is much faster and easier than sharing information between processes. The operating system handles the details of thread creation and coordination. On a single-processor system, the operating system allocates processor time to each of an application's threads, so a single thread doesn't block the rest of the application. On multi-processor systems, the operating system can even run threads on separate processors, so that threads truly can run simultaneously. The drawback to traditional multi-threaded programming is that it can be difficult to design a thread-safe application — that is, an application in which one thread doesn't corrupt the resources being used by another thread. Because all resources are shared in a multi-threaded application, you need to use various locking and scheduling mechanisms to guard against multiple threads modifying resources concurrently. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Thread Support in Tcl Tcl added support for multi-threaded programming in version 8.1. The Tcl core was made thread-safe. Furthermore, new C functions exposed "platform-neutral" thread functionality. However, no official support was provided for multi-threaded scripting. Since then, the Thread extension — originally written by Brent Welch and currently maintained by Zoran Vasiljevic — has become the accepted mechanism for creating multi-threaded Tcl scripts. The most recent version of the Thread extension as this was being written was 2.5. In general, this version requires Tcl 8.3 or later, and several of the commands provided require Tcl 8.4 or later. At the C programming level, Tcl's threading model requires that a Tcl interpreter be managed by only one thread. However, each thread can create as many Tcl interpreters as needed running under its control. As is the case in even a single-threaded application, each Tcl interpreter has its own set of variables and procedures. A thread can execute commands in another thread's Tcl interpreter only by sending special messages to that interpreter's event queue. Those messages are handled in the order received along with all other types of events. Obtaining a Thread-Enabled Tcl Interpreter Most binary distributions of Tcl are not thread-enabled, because the default options for building the Tcl interpreters and libraries do not enable thread support. Thread safety adds overhead, slowing down single-threaded Tcl applications, which constitute the vast majority of Tcl applications. Also, many Tcl extensions aren't thread safe, and naively trying to use them in a multi-threaded application can cause errors or crashes. Unless you can obtain a thread-enabled binary distribution of Tcl, you must compile your own from the Tcl source distribution. This requires running the configure command with the --enable-threads option during the build process. (See Chapter 48, "Compiling Tcl and Extensions" for more information.) You can test whether a particular Tcl interpreter is thread-enabled by checking for the existence of the tcl_platform(threaded) element. This element exists and contains a Boolean true value in thread-enabled interpreters, whereas it doesn't exist in interpreters without thread support. Using Extensions in Multi-Threaded Scripts Because each interpreter has its own set of variables and procedures, you must explicitly load an extension into each thread that wants to use it. Only the Thread extension itself is automatically loaded into each interpreter. You must be careful when using extensions in multi-threaded scripts. Many Tcl extensions aren't thread-safe. Attempting to use them in multi-threaded scripts often results in crashes or corrupted data. Tcl-only extensions are generally thread-safe. Of course, they must make no use of other commands or extensions that aren't thread-safe. But otherwise, multi-threaded operation doesn't add any new issues that don't already affect single-threaded scripts. You should always assume that a binary extension is not thread-safe unless its documentation explicitly says that it is. And even thread-safe binary extensions must be compiled with thread support enabled for you to use them in multi-threaded applications. (The default compilation This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks options for most binary extensions don't include thread support.) Tk isn't truly thread-safe. Most underlying display libraries (such as X Windows) aren't thread safe — or at least aren't typically compiled with thread-safety enabled. However, significant work has gone into making the Tk core thread-safe. The result is that you can safely use Tk in a multi-threaded Tcl application as long as only one thread uses Tk commands to manage the interface. Any other thread that needs to update the interface should send messages to the thread controlling the interface. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Getting Started with the Thread Extension You start a thread-enabled tclsh or wish the same as you would a non-threadedtclsh or wish. When started, there is only one thread executing, often referred to as the main thread, which contains a single Tcl interpreter. If you don't create any more threads, your application runs like any other single-threaded application. Make sure that the main thread is the last one to terminate. The main thread has a unique position in a multi-threaded Tcl script. If it exits, then the entire application terminates. Also, if the main thread terminates while other threads still exist, Tcl can sometimes crash rather than exiting cleanly. Therefore, you should always design your multi-threaded applications so that your main thread waits for all other threads to terminate before it exits. Before accessing any threading features from your application, you must load the Thread extension: package require Thread The Thread extension automatically loads itself into any new threads your application creates withthread::create. All other extensions must be loaded explicitly into each thread that needs to use them. The Thread extension creates commands in three separate namespaces: The thread namespace contains all of the commands for creating and managing threads, including inter-thread messaging, mutexes, and condition variables. The tsv namespace contains all of the commands for creating and managing thread shared variables. The tpool namespace contains all of the commands for creating and managing thread pools. Creating Threads The thread::create command creates a new thread containing a new Tcl interpreter. Any thread can create another thread at will; you aren't limited to starting threads from only the main thread. The thread::create command returns immediately, and its return value is the ID of the thread created. The ID is a unique token that you use to interact with and manipulate the thread, in much the same way as you use a channel identifier returned by open to interact with and manipulate that channel. There are several commands available for introspection on thread IDs: thread::id returns the ID of the current thread; thread::names returns a list of threads currently in existence; and thread::exists tests for the existence of a given thread. The thread::create command accepts a Tcl script as an argument. If you provide a script, the interpreter in the newly created thread executes This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks it and then terminates the thread. Example 21-1 demonstrates this by creating a thread to perform a recursive search for files in a directory. For a large directory structure, this could take considerable time. By performing the search in a separate thread, the main thread is free to perform other operations in parallel. Also note how the "worker" thread loads an extension and opens a file, completely independent of any extensions loaded or files opened in other threads. Example 21-1 Creating a separate thread to perform a lengthy operation package require Thread # Create a separate thread to search the current directory # and all its subdirectories, recursively, for all files # ending in the extension ".tcl". Store the results in the # file "files.txt". thread::create { # Load the Tcllib fileutil package to use its # findByPattern procedure. package require fileutil set files [fileutil::findByPattern [pwd] *.tcl] set fid [open files.txt w] puts $fid [join $files \n] close $fid } # The main thread can perform other tasks in parallel... If you don't provide a script argument to thread::create, the thread's interpreter enters its event loop. You then can use thethread::send command, described on page 328, to send it scripts to evaluate. Often though, you'd like to perform some initialization of the thread before having it enter its event loop. To do so, use the thread::wait command to explicitly enter the event loop after performing any desired initialization, as shown in Example 21-2. You should always usethread::wait to cause a thread to enter its event loop, rather thanvwait or tkwait, for reasons discussed in "Preserving and Releasing Threads" on page 330. Example 21-2 Initializing a thread before entering its event loop set httpThread [thread::create { package require http thread::wait }] After creating a thread, never assume that it has started executing. There is a distinction between creating a thread and starting execution of a thread. When you create a thread, the operating system allocates resources for the thread and prepares it to run. But after creation, the thread might not start execution immediately. It all depends on when the This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks operating system allocates execution time to the thread. Be aware that the thread::create command returns when the thread is created, not necessarily when it has started. If your application has any inter-thread timing dependencies, always use one of the thread synchronization techniques discussed in this chapter. Creating Joinable Threads Remember that the main thread must be the last to terminate. Therefore you often need some mechanism for determining when it's safe for the main thread to exit. Example 21-3 shows one possible approach: periodically checkingthread::names to see if the main thread is the only remaining thread. Example 21-3 Creating several threads in an application package require Thread puts "*** I'm thread [thread::id]" # Create 3 threads for {set thread 1} {$thread <= 3} {incr thread} { set id [thread::create { # Print a hello message 3 times, waiting # a random amount of time between messages for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Thread [thread::id] says hello" } }] ;# thread::create puts "*** Started thread $id" } ;# for puts "*** Existing threads: [thread::names]" # Wait until all other threads are finished while {[llength [thread::names]] > 1} { after 500 } puts "*** That's all, folks!" A better approach in this situation is to use joinable threads, which are supported in Tcl 8.4 or later. A joinable thread allows another thread to wait upon its termination with the thread::join command. You can use thread::join only with joinable threads, which are created by including the thread::create -joinable option. Attempting to join a thread not created with-joinable results in an error. Failing to join a joinable thread causes memory and other resource leaks in your application. Example 21-4 revises the program from Example 21-3 to use joinable threads. Example 21-4 Using joinable threads to detect thread termination . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks package require Thread puts "*** I'm thread [thread::id]" # Create 3 threads for {set thread 1} {$thread <= 3} {incr thread} { set id [thread::create -joinable { # Print a hello message 3 times, waiting # a random amount of time between messages for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Thread [thread::id] says hello" } }] ;# thread::create puts "*** Started thread $id" lappend threadIds $id } ;# for puts "*** Existing threads: [thread::names]" # Wait until all other threads are finished foreach id $threadIds { thread::join $id } puts "*** That's all, folks!" The thread::join command blocks. Be aware that thread::join blocks. While the thread is waiting for thread::join to return, it can't perform any other operations, including servicing its event loop. Therefore, make sure that you don't use thread::join in situations where a thread must be responsive to incoming events. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Sending Messages to Threads The thread::send command sends a script to another thread to execute. The target thread's main interpreter receives the script as a special type of event added to the end of its event queue. A thread evaluates its messages in the order received along with all other types of events. Obviously, a thread must be in its event loop for it to detect and respond to messages. As discussed on page 324, a thread enters its event loop if you don't provide a script argument to thread::create, or if you include thethread::wait command in the thread's initialization script. Synchronous Message Sending By default, thread::send blocks until the target thread finishes executing the script. The return value ofthread::send is the return value of the last command executed in the script. If an error occurs while evaluating the script, the error condition is "reflected" into the sending thread; thread::send generates the same error code, and the target thread's stack trace is included in the value of the errorInfo variable of the sending thread: Example 21-5 Examples of synchronous message sending set t [thread::create] ;# Create a thread => 1572 set myX 42 ;# Create a variable in the main thread => 42 # Copy the value to a variable in the worker thread thread::send $t [list set yourX $myX] => 42 # Perform a calculation in the worker thread thread::send $t {expr { $yourX / 2 } } => 21 thread::send $t {expr { $yourX / 0 } } => divide by zero catch {thread::send $t {expr { $yourX / 0 } } } ret => 1 puts $ret => divide by zero puts $errorInfo => divide by zero while executing "expr { $yourX / 0 } " invoked from within "thread::send $t {expr { $yourX / 0 } } " If you also provide the name of a variable to a synchronous thread::send, then it behaves analogously to acatch command; thread::send returns the return code of the script, and the return value of the last command executed in the script — or the error message — is stored in the variable. Tcl stores the target thread's stack trace in the sending thread's errorInfo variable. Example 21-6 Using a return variable with synchronous message sending . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks thread::send $t {incr yourX 2} myY => 0 puts $myY => 44 thread::send $t {expr { acos($yourX) } } ret => 1 puts $ret => domain error: argument not in valid range puts $errorInfo => domain error: argument not in valid range while executing "expr { acos($yourX) } " While the sending thread is waiting for a synchronous thread::send to return, it can't perform any other operations, including servicing its event loop. Therefore, synchronous sending is appropriate only in cases where: you want a simple way of getting a value back from another thread; you don't mind blocking your thread if the other thread takes a while to respond; or you need a response from the other thread before proceeding. Watch out for deadlock conditions with synchronous message sending. If Thread A performs a synchronous thread::send to Thread B, and while evaluating the script Thread B performs a synchronous thread::send to Thread A, then your application is deadlocked. Because Thread A is blocked in its thread::send, it is not servicing its event loop, and so can't detect Thread B's message. This situation arises most often when the script you send calls procedures in the target thread, and those procedures contain thread::send commands. Under these circumstances, it might not be obvious that the script sent will trigger a deadlock condition. For this reason, you should be cautious about using synchronous thread::send commands for complex actions. Sending in asynchronous mode, described in the next section, avoids potential deadlock situations like this. Asynchronous Message Sending With the -async option, thread::send sends the script to the target thread in asynchronous mode. In this case,thread::send returns immediately. By default, an asynchronous thread::send discards any return value of the script. However, if you provide the name of a variable as an additional argument to thread::send, the return value of the last command executed in the script is stored as the value of the variable. You can then either vwait on the variable or create a write trace on the variable to detect when the target thread responds. For example: thread::send -async $t [list ProcessValues $vals] result vwait result This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks In this example, the thread::send command returns immediately; the sending thread could then continue with any other operations it needed to perform. In this case, it executes a vwait on the return variable to wait until the target thread finishes executing the script. However, while waiting for the response, it can detect and process incoming events. In contrast, the following synchronous thread::send blocks, preventing the sending thread from processing events until it receives a response from the target thread: thread::send $t [list ProcessValues $vals] result [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Preserving and Releasing Threads A thread created with a script not containing a thread::wait command terminates as soon as the script finishes executing. But if a thread enters its event loop, it continues to run until its event loop terminates. So how do you terminate a thread's event loop? Each thread maintains an internal reference count. The reference count is set initially to 0, or to 1 if you create the thread with the thread::create -preserved option. Any thread can increment the reference count afterwards by executingthread::preserve, and decrement the reference count by executing thread::release. These commands affect the reference count of the current thread unless you specify the ID of another thread. If a call to thread::release results in a reference count of 0 or less, the thread is marked for termination. The use of thread reference counts allows multiple threads to preserve the existence of a worker thread until all of the threads release the worker thread. But the majority of multi-threaded Tcl applications don't require that degree of thread management. In most cases, you can simply create a thread and then later use thread::release to terminate it: set worker [thread::create] thread::send -async $worker $script # Later in the program, terminate the worker thread thread::release $worker A thread marked for termination accepts no further messages and discards any pending events. It finishes processing any message it might be executing currently, then exits its event loop. If the thread entered its event loop through a call to thread::wait, any other commands following thread::wait are executed before thread termination, as shown inExample 21-7. This can be useful for performing "clean up" tasks before terminating a thread. Example 21-7 Executing commands after thread::wait returns set t [thread::create { puts "Starting worker thread" thread::wait # This is executed after the thread is released puts "Exiting worker thread" }] Note that if a thread is executing a message script when thread::release is called (either by itself or another thread), the thread finishes executing its message script before terminating. So, if a thread is stuck in an endless loop, calling thread::release has no effect on the thread. In fact, there is no way to kill such a "runaway thread." Always use thread::wait to enter a thread's event loop. This system for preserving and releasing threads works only if you use the thread::wait command to enter the thread's event loop (or if you . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks did not provide a creation script when creating the thread). If you use vwait or tkwait to enter the event loop,thread::release cannot terminate the thread. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Error Handling If an error occurs while a thread is executing its creation script (provided by thread::create), the thread dies. In contrast, if an error occurs while processing a message script (provided by thread::send), the default behavior is for the thread to stop execution of the message script, but to return to its event loop and continue running. To cause a thread to die when it encounters an uncaught error, use the thread::configure command to set the thread's -unwindonerror option to true: thread::configure $t -unwindonerror 1 Error handling is determined by the thread creating the thread or sending the message. If an error occurs in a script sent by a synchronous thread::send, then the error condition is "reflected" to the sending thread, as described in Synchronous " Message Sending" on page 328. If an error occurs during thread creation or an asynchronous thread::send, the default behavior is for Tcl to send a stack trace to the standard error channel. Alternatively, you can specify the name of your own custom error handling procedure with thread::errorproc. Tcl automatically calls your procedure whenever an "asynchronous" error occurs, passing it two arguments: the ID of the thread generating the error, and the stack trace. (This is similar to defining your own bgerror procedure, as described in "The bgerror Command" on page 202.) For example, the following code logs all uncaught errors to the file errors.txt: Example 21-8 Creating a custom thread error handler set errorFile [open errors.txt a] proc logError {id error} { global errorFile puts $errorFile "Error in thread $id" puts $errorFile $error puts $errorFile "" } thread::errorproc logError [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Shared Resources The present working directory is a resource shared by all interpreters in all threads. If one thread changes the present working directory, then that change affects all interpreters and all threads. This can pose a significant problem, as some library routines temporarily change the present working directory during execution, and then restore it before returning. But in a multi-threaded application, another thread could attempt to access the present working directory during this period and get incorrect results. Therefore, the safest approach if your application needs to access the present working directory is to store this value in a global or thread-shared variable before creating any other threads. The following example uses tsv::set to store the current directory in the pwd element of the application shared variable: package require Thread # Save the pwd in a thread-shared variable tsv::set application pwd [pwd] set t [thread::create {#...}] Environment variables are another shared resource. If one thread makes a change to an environment variable, then that change affects all threads in your application. This might make it tempting to use the global env array as a method for sharing information between threads. However, you should not do so, because it is far less efficient than thread-shared variables, and there are subtle differences in the way environment variables are handled on different platforms. If you need to share information between threads, you should instead use thread-shared variables, as discussed in "Shared Variables" on page 337. The exit command kills the entire application. Although technically not a shared resource, it's important to recognize that the exit command kills the entire application, no matter which thread executes it. Therefore, you should never call exit from a thread when your intention is to terminate only that thread. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Managing I/O Channels Channels are shared resources in most programming languages. But in Tcl, channels are implemented as a per-interpreter resource. Only the standard I/O channels (stdin, stdout, and stderr) are shared. Be careful with standard I/O channel on Windows and Macintosh. When running wish on Windows and Macintosh prior to OS X, you don't have real standard I/O channels, but simulated stdout and stderr channels direct output to the special console window. As of Thread 2.5, these simulated channels appear in the main thread's channel list, but not in any other thread's channel list. Therefore, you'll cause an error if you attempt to access these channels from any thread other than the main thread. Accessing Files from Multiple Threads In a multi-threaded application, avoid having the same file open in multiple threads. Having the same file open for read access in multiple threads is safe, but it is more efficient to have only one thread read the file and then share the information with other threads as needed. Opening the same file in multiple threads for write or append access is likely to fail. Operating systems typically buffer information written to a disk on a per-channel basis. With multiple channels open to the same file, it's likely that one thread will end up overwriting data written by another thread. If you need multiple threads to have write access to a single file, it's far safer to have one thread responsible for all file access, and let other threads send messages to the thread to write the data. Example 21-9 shows the skeleton implementation of a logging thread. Once the log file is open, other threads can call the logger's AddLog procedure to write to the log file. Example 21-9 A basic implementation of a logging thread set logger [thread::create { proc OpenLog {file} { global fid set fid [open $file a] } proc CloseLog {} { global fid close $fid } proc AddLog {msg} { global fid puts $fid $msg This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } thread::wait }] Transferring Channels between Threads As long as you're working with Tcl 8.4 or later, the Thread extension gives you the ability to transfer a channel from one thread to another with the thread::transfer command. After the transfer, the initial thread has no further access to the channel. The symbolic channel ID remains the same in the target thread, but you need some method of informing the target thread of the ID, such as a thread-shared variable. The thread::transfer command blocks until the target thread has incorporated the channel. The following shows an example of transferring a channel, and simply duplicating the value of the channel ID in the target thread rather than using a thread-shared variable: set fid [open myfile.txt r] # ... set t [thread::create] thread::transfer $t $fid # Duplicate the channel ID in the target thread thread::send $t [list set fid $fid] Another option for transferring channels introduced in Thread 2.5 is thread::detach, which detaches a channel from a thread, and thread::attach, which attaches a previously detached channel to a thread. The advantage to this approach is that the thread relinquishing the channel doesn't need to know which thread will be acquiring it. This is useful when your application uses thread pools, which are described on page 342. The ability to transfer channels between threads is a key feature in implementing a multi-thread server, in which a separate thread is created to service each client connected. One thread services the listening socket. When it receives a client connection, it creates a new thread to service the client, then transfers the client's communication socket to that thread. Transferring socket channels requires special handling. A complication arises in that you can't perform the transfer of the communication socket directly from the connection handler, like this: socket -server ClientConnect 9001 proc ClientConnect {sock host port} { set t [thread::create { ... }] # The following command fails thread::transfer $t $sock } The reason is that Tcl maintains an internal reference to the communication socket during the connection callback. The thread::transfer command (and the thread::detach command) cannot transfer the channel while this additional reference is in place. Therefore, we must use the after command to defer the transfer until after the connection callback returns, as shown inExample 21-10. Example 21-10 Deferring socket transfer until after the connection callback . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks proc _ClientConnect {sock host port} { after 0 [list ClientConnect $sock $host $port] } proc ClientConnect {sock host port} { # Create the client thread and transfer the channel } One issue in early versions of Tcl 8.4 was a bug that failed to initialize Tcl's socket support when a socket channel was transferred into a thread. The work-around for this bug is to explicitly create a socket in the thread (which can then be immediately closed) to initialize the socket support, and then transfer the desired socket. This bug has been fixed, but Example 21-11 illustrates how you can perform extra initialization in a newly created thread before it enters its event loop: Example 21-11 Working around Tcl's socket transfer bug by initializing socket support set t [thread::create { # Initialize socket support by opening and closing # a server socket. close [socket -server {} 0] # Now sockets can be transferred safely into this thread. thread::wait }] Example 21-12 integrates all of these techniques to create a simple multi-threaded echo server. Note that the server still uses event-driven interaction in each client thread. Technically, this isn't necessary for such a simple server, because once a client thread starts it doesn't expect to receive messages from any other thread. If a thread needs to respond to messages from other threads, it must be in its event loop to detect and service such messages. Because this requirement is common, this application demonstrates the event-driven approach. Example 21-12 A multi-threaded echo server package require Tcl 8.4 package require Thread 2.5 if {$argc > 0} { set port [lindex $argv 0] } else { set port 9001 } socket -server _ClientConnect $port proc _ClientConnect {sock host port} { # Tcl holds a reference to the client socket during # this callback, so we can't transfer the channel to our # worker thread immediately. Instead, we'll schedule an # after event to create the worker thread and transfer # the channel once we've re-entered the event loop. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks after 0 [list ClientConnect $sock $host $port] } proc ClientConnect {sock host port} { # Create a separate thread to manage this client. The # thread initialization script defines all of the client # communication procedures and puts the thread in its # event loop. set thread [thread::create { proc ReadLine {sock} { if {[catch {gets $sock line} len] || [eof $sock]} { catch {close $sock} thread::release } elseif {$len >= 0} { EchoLine $sock $line } } proc EchoLine {sock line} { if {[string equal -nocase $line quit]} { SendMessage $sock \ "Closing connection to Echo server" catch {close $sock} thread::release } else { SendMessage $sock $line } } proc SendMessage {sock msg} { if {[catch {puts $sock $msg} error]} { puts stderr "Error writing to socket: $error" catch {close $sock} thread::release } } # Enter the event loop thread::wait }] # Release the channel from the main thread. We use # thread::detach/thread::attach in this case to prevent # blocking thread::transfer and synchronous thread::send # commands from blocking our listening socket thread. thread::detach $sock # Copy the value of the socket ID into the # client's thread thread::send -async $thread [list set sock $sock] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Attach the communication socket to the client-servicing # thread, and finish the socket setup. thread::send -async $thread { thread::attach $sock fconfigure $sock -buffering line -blocking 0 fileevent $sock readable [list ReadLine $sock] SendMessage $sock "Connected to Echo server" } } vwait forever [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] Shared Variables Standard Tcl variables are a per-interpreter resource; an interpreter has no access to variables in another interpreter. For the simple exchange of information between threads, you can substitute the values of variables into a script that you send to another thread, and obtain the return value of a script evaluated by another thread. But this technique is inadequate for sharing information among multiple threads, and inefficient when transferring large amounts of information. The Thread extension supports the creation of thread-shared variables, which are accessible by all threads in an application. Thread-shared variables are stored independent of any interpreter, so if the thread that originally created a shared variable terminates, the shared variable continues to exist. Shared variables are stored in collections called arrays. The term is somewhat unfortunate, because while shared variable arrays are similar to standard Tcl arrays, they do not use the same syntax. Your application can contain as many shared variable arrays as you like. Because of the special nature of shared variables, you cannot use the standard Tcl commands to create or manipulate shared variables, or use standard variable substitution syntax to retrieve their values. (This also means that you cannot use shared variables as a widget's -textvariable or -listvariable, with vwait or tkwait, or with variable traces.) All commands for interacting with shared variables are provided by the Thread extension in the tsv namespace. Most of the tsv commands are analogous to Tcl commands for creating and manipulating standard Tcl variables. Table 21-3 on page 346 describes all of the tsv commands. You create a shared variable with tsv::set, specifying the array name, the variable name (sometimes also referred to as the shared array element), and the value to assign to it. For example: tsv::set application timeout 10 To retrieve the value of a shared variable, either usetsv::set without a value or calltsv::get. The two commands shown below are equivalent: tsv::set application timeout tsv::get application timeout All shared variable commands are guaranteed to be atomic. A thread locks the variable during the entire command. No other thread can access the variable until the command is complete; if a thread attempts to do so, it blocks until the variable is unlocked. This simplifies the use of shared variables in comparison to most other languages, which require explicit locking and unlocking of variables to prevent possible corruption from concurrent access by multiple threads. This locking feature is particularly useful in the class of tsv commands that manipulate lists. Standard Tcl commands likelinsert and lreplace take a list value as input, and then return a new list as output. Modifying the value of a list stored in a standard Tcl variable requires a sequence like this: set states [linsert $states 1 California Nevada] Doing the same with shared variables is problematic: tsv::set common cities \ [linsert [tsv::get common cities] 1 Yreka Winnemucca] After reading the shared variable with tsv::get, another thread could modify the value of the variable before thetsv::set command executes, resulting in data corruption. For this reason, the tsv commands that manipulate list values actually modify the value of the shared variable. Data corruption by another thread won't occur because the shared variable is locked during the entire execution of the command: tsv::linsert common cities 1 Yreka Winnemucca This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Mutexes and Condition Variables Mutexes and condition variables are thread synchronization mechanisms. Although they are used frequently in other languages, they aren't needed as often in Tcl because of Tcl's threading model and the atomic nature of all shared variable commands. All mutex and condition variable commands are provided by the Thread extension in the thread namespace. Mutexes A mutex, which is short for mutual exclusion, is a locking mechanism. You use a mutex to protect shared resources — such as shared variables, serial ports, databases, etc. — from concurrent access by multiple threads. Before accessing the shared resource, the thread attempts to lock the mutex. If no other thread currently holds the mutex, the thread successfully locks the mutex and can access the resource. If another thread already holds the mutex, then the attempt to lock the mutex blocks until the other thread releases the mutex. This sequence is illustrated in Example 21-13. The first step is creating a mutex with thethread::mutex create operation, which returns a unique token representing the mutex. The same token is used in all threads, and so you must make this token available (for example, through a shared variable) to all threads that access the shared resource. Example 21-13 Using a mutex to protect a shared resource # Create the mutex, storing the mutex token in a shared # variable for other threads to access. tsv::set db mutex [thread::mutex create] # ... # Lock the mutex before accessing the shared resource. thread::mutex lock [tsv::get db mutex] # Use the shared resource, and then unlock the mutex. thread::mutex unlock [tsv::get db mutex] # Lather, rinse, repeat as needed... thread::mutex destroy [tsv::get db mutex] Mutexes rely on threads being "good citizens." . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Mutexes work only if all threads in an application use them properly. A "rogue" thread can ignore using a mutex and access the shared resource directly. Therefore, you should be very careful to use your mutexes consistently when designing and implementing your application. Condition Variables A condition variable is a synchronization mechanism that allows one or more threads to sleep until they receive notification from another thread. A condition variable is associated with a mutex and a boolean condition known as a predicate. A thread uses the condition variable to wait until the boolean predicate is true. A different thread changes the state of the predicate to true, and then notifies the condition variable. The mutex synchronizes thread access to the data used to compute the predicate value. The general usage pattern for the signalling thread is: Lock the mutex Change the state so the predicate is true Notify the condition variable Unlock the mutex The pattern for a waiting thread is: Lock the mutex Check the predicate If the predicate is false, wait on the condition variable until notified Do the work Unlock the mutex In practice, a waiting thread should always check the predicate inside a while loop, because multiple threads might be waiting on the same condition variable. A waiting thread automatically releases the mutex when it waits on the condition variable. When the signalling thread notifies the condition variable, all threads waiting on that condition variable compete for a lock on the mutex. Then when the signalling thread releases the mutex, one of the waiting threads gets the lock. It is quite possible for that thread then to change the state so that the predicate is no longer true when it releases the lock. For example, several worker threads forming a thread pool might wait until there is some type of job to process. Upon notification, the first worker thread takes the job, leaving nothing for the other worker threads to process. This sequence for using a condition variable sounds complex, but is relatively easy to code. Example 21-14 shows the sequence for the signalling thread. The first step is creating a condition variable with the thread::cond create operation, which returns a unique token representing the condition variable. As with mutexes, the same token is used in all threads, and so you must make this token available (for example, through a shared variable) to all threads that access the condition variable. When the thread is ready to update the predicate, it first locks the associated mutex. Then it notifies the condition variable with thread::cond notify and finally unlocks the mutex. Example 21-14 Standard condition variable use for a signalling thread # Create the condition variable and accompanying mutex. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Use shared variables to share these tokens with all other # threads that need to access them. set cond [tsv::set tasks cond [thread::cond create]] set mutex [tsv::set tasks mutex [thread::mutex create]] # When we're ready to update the state of the predicate, we # must first obtain the mutex protecting it. thread::mutex lock $mutex # Now update the predicate. In this example, we'll just set a # shared variable to true. In practice, the predicate can be # more complex, such as the length of a list stored in a # shared variable being greater than 0. tsv::set tasks predicate 1 # Notify the condition variable, waking all waiting threads. # Each thread will block until it can lock the mutex. thread::cond notify $cond # Unlock the mutex. thread::mutex unlock $mutex Example 21-15 shows the sequence for a waiting thread. When a thread is ready to test the predicate, it must first lock the mutex protecting it. If the predicate is true, the thread can continue processing, unlocking the mutex when appropriate. If the predicate is false, the thread executes thread::cond wait to wait for notification. The thread::cond wait command atomically unlocks the mutex and puts the thread into a wait state. Upon notification, the thread atomically locks the mutex (blocking until it can obtain it) and returns from the thread::cond wait command. It then tests the predicate, and repeats the process until the predicate is true. Example 21-15 Standard condition variable use for a waiting thread set mutex [tsv::get tasks mutex] set cond [tsv::get tasks cond] # Lock the mutex before testing the predicate. thread::mutex lock $mutex # Test the predicate, if necessary waiting until it is true. while {![tsv::get tasks predicate]} { # Wait for notification on the condition variable. # thread::cond wait internally unlocks the mutex, # blocks until it receives notification, then locks # the mutex again before returning. thread::cond wait $cond $mutex } # We now hold the mutex and know the predicate is true. Do This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # whatever processing is desired, and unlock the mutex when # it is no longer needed. thread::mutex unlock $mutex Tcl's threading model greatly reduces the need for condition variables. It's usually much simpler to place a thread in its event loop with thread::wait, and then send it messages withthread::send. And for applications where you want a thread pool to handle jobs on demand, the Thread extension's built-in thread pool implementation is far easier than creating your own with condition variables. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Thread Pools A thread pool is a common multi-threaded design pattern. A thread pool consists of several worker threads that wait for jobs to perform. When a job is sent to the thread pool, one of the available worker threads processes it. If all worker threads are busy, either additional worker threads are created to handle the incoming jobs, or the jobs are queued until worker threads are available. The tpool namespace of the Thread extension provides several commands for creating and managing thread pools. Using these commands is much easier than trying to build your own thread pools from scratch using mutexes, condition variables, etc. Thread pool support was added to the Thread extension in version 2.5. The tpool::create command creates a thread pool, returning the ID of the new thread pool. There are several options totpool::create that allow you to configure the behavior of the thread pool. The -minthreads option specifies the minimum number of threads in the pool. This number of threads is created when the thread pool is created, and as worker threads in the pool terminate, new worker threads are created to bring the number up to this minimum. The -maxthreads option specifies the maximum number of worker threads allowed. If a job is posted to the thread pool and there are no idle worker threads available, a new worker thread is created to handle the job only if the number of worker threads won't exceed the maximum number. If the maximum has been reached, the job is queued until a worker thread is available. The -idletime option specifies the number of seconds that a worker thread waits for a new job before terminating itself to preserve system resources. And the -initcmd and -exitcmd options provide scripts to respectively initialize newly created worker threads and clean up exiting worker threads. Once you have created a thread pool, you send jobs to it with the tpool::post command. A job consists of an arbitrary Tcl script to execute. The job is executed by the first available worker thread in the pool. If there are no idle worker threads, a new worker thread is created, as long as the number of worker threads doesn't exceed the thread pool maximum. If a new worker thread can't be created, the tpool::post command blocks until a worker thread can handle the job, but while blocked the posting thread still services its event loop. The return value of tpool::post is a job ID. To receive notification that a job is complete, your thread must calltpool::wait. The tpool::wait command blocks, but continues to service the thread's event loop while blocked. Additionally, the tpool::wait command can wait for several jobs simultaneously, returning when any of the jobs are complete. The return value of tpool::wait is a list of completed job IDs. After tpool::wait reports that a job is complete, you can call tpool::get to retrieve the result of the job, which is the return value of the last command executed in the job script. If the job execution resulted in an error, the error is "reflected" to the posting thread: tpool::get raises an error and the values of errorInfo and errorCode are updated accordingly. Finally, a thread pool can be preserved and released in much the same way as an individual thread. Each thread pool maintains an internal reference count, which is initially set to 0 upon creation. Any thread can increment the reference count afterwards by executing tpool::preserve, and decrement the reference count by executingtpool::release. If a call to tpool::release results in a reference count of 0 or less, the thread pool is marked for termination. Any further reference to a thread pool once it is marked for termination results in an error. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The Thread Package Commands The commands of the Thread extension are grouped into three separate namespaces, based on their functionality. This section summarizes the commands found in each namespace. The thread Namespace The thread namespace contains all of the commands for creating and managing threads, including inter-thread messaging, mutexes, and condition variables. Table 21-1 describes all of the commands contained in thethread namespace. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 21-1. The commands of the thread namespace thread::attach channel Attaches the previously detached channel into current interpreter of the current thread. thread::cond create Returns a token for a newly created condition variable. thread::cond destroy cond Destroys the specified condition variable. thread::cond notify cond Wakes up all threads waiting on the specified condition variable. thread::cond wait cond Blocks until the specified condition variable is signaled by another thread with thread::cond notify, or until the mutex ?ms? optional timeout in milliseconds specified by ms expires. The mutex must be locked by the calling thread before calling thread::cond wait. While waiting on the cond, the command releases mutex. Before returning to the calling thread, the command re-acquires mutex again. thread::configure id ?option?value? ?option Queries or sets thread configuration options, as described in Table 21-2. value...? thread::create ?-joinable? Creates a thread, returning the thread's ID. The -joinable flag allows another thread to wait for termination of ?-preserved? ?script? this thread with thread::join. The -preserved flag sets the thread's initial reference count to 1, rather than the default of 0. (See thread::preserve and thread::release.) If provided, the thread executes the script, then exits; otherwise, it enters an events loop to wait for messages. thread::detach channel Detaches the specified channel from the current thread so that it no longer has access to it. Any single thread can then thread::attach the channel to gain access to it. thread::errorproc ?proc? Registers a procedure to handle errors that occur when performing asynchronous thread::send commands. When called, proc receives two argument: the ID of the thread that generated the error, and the value of that thread's errorInfo variable. thread::eval ?-lock mutex? Concatenates the arguments and evaluates the resulting script under the mutex protection. If no mutex is arg ?arg...? specified, an internal static one is used for the duration of the evaluation. thread::exists id Returns boolean indicating whether or not the specified thread exists. thread::id Returns the current thread's ID. thread::join id Blocks until the target thread terminates. (Available only with Tcl 8.4 or later.) thread::mutex create Returns a token for a newly created mutex. thread::mutex destroy mutex Destroys the mutex. thread::mutex lock mutex Locks the mutex, blocking until it can gain exclusive access. thread::mutex unlock mutex Unlocks the mutex. thread::names Returns a list of the IDs of all running threads. thread::preserve ?id? Increments the reference count of the indicated thread, or the current thread if noid is given. thread::release ?-wait? Decrements the reference count of the indicated thread, or the current thread if no id is given. If the reference ?id? count is 0 or less, mark the thread for termination. If -wait is specified, the command blocks until the target thread terminates. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks thread::send ?-async? id Sends the script, to thread id. If -async is specified, do not wait for script to complete. Stores the result of script script ?varname? in varname, if provided. thread::transfer id Transfers the open channel from the current thread to the main interpreter of the target thread. This command channel blocks until the target thread incorporates the channel. (Available only with Tcl 8.4 or later.) thread::unwind Terminates a prior thread::wait to cause a thread to exit. Deprecated in favor ofthread::release. thread::wait Enters the event loop. The thread::configure command allows an application to query and set thread configuration options, in much the same way as thefconfigure command configures channels. Table 21-2 lists the available thread configuration options. Table 21-2. Thread configuration options -eventmark int Specifies the maximum number of pending scripts sent with thread::send that the thread accepts. Once the maximum is reached, subsequent thread::send messages to this script block until the number of pending scripts drops below the maximum. A value of 0 (default) allows an unlimited number of pending scripts. -unwindonerror boolean If true, the thread "unwinds" (terminates its event loop) on uncaught errors. Default is false. The tsv Namespace The tsv namespace contains all of the commands for creating and managing thread shared variables.Table 21-3 describes all of the commands contained in the tsv namespace. Table 21-3. The commands of the tsv namespace tsv::append array element value Appends to the shared variable likeappend. ?value ...? tsv::exists array ?element? Returns boolean indicating whether the given element exists, or if no element is given, whether the shared array exists. tsv::get array element Returns the value of the shared variable. If varname is provided, the value is stored in the variable, and ?varname? the command returns 1 if the element existed, 0 otherwise. tsv::incr array element Increments the shared variable likeincr. ?increment? tsv::lappend array element Appends elements to the shared variable likelappend. value ?value ...? tsv::lindex array element index Returns the indicated element from the shared variable, similar tolindex. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks tsv::linsert array element index Atomically inserts elements into the shared variable, similar tolinsert, but actually modifying the variable. value ?value ...? tsv::llength array element Returns the number of elements in the shared variable, similar tollength. tsv::lock array arg ?arg ...? Concatenates the args and evaluates the resulting script. During script execution, the command locks the specified shared array with an internal mutex. tsv::lpop array element ?index? Atomically deletes the value at the index list position from the shared variable and returns the value deleted. The default index is 0. tsv::lpush array element value Atomically inserts the value at the index list position in the shared variable. The defaultindex is 0. ?index? tsv::lrange array element first Returns the indicated range of elements from the shared variable, similar tolrange. last tsv::lreplace array element Atomically replaces elements in the shared variable, similar to lreplace, but actually modifying the value ?value ...? variable. tsv::lsearch array element Returns the index of the first element in the shared variable matching the pattern, similar to lsearch. ?mode? pattern Supported modes are: -exact, -glob (default), and -regexp. tsv::move array old new Atomically renames the shared variable fromold to new. tsv::names ?pattern? Returns a list of all shared variable arrays, or those whose names match the optional glob pattern. tsv::object array element Creates and returns the name of an accessor command for the shared variable. Other tsv commands are available as subcommands of the accessor to manipulate the shared variable. tsv::pop array element Atomically returns the value of the shared variable and deletes theelement. tsv::set array element ?value? Sets the value of the shared variable, creating it if necessary. Ifvalue is omitted, the current value is returned. tsv::unset array ?element? Deletes the shared variable, or the entirearray if no element is specified. The tpool Namespace The tpool namespace contains all of the commands for creating and managing thread pools.Table 21-4 describes all of the commands contained in the tpool namespace. Table 21-4. The commands of the tpool namespace tpool::create Creates a thread pool, returning the thread pool's ID.Table 21-5 describes supported configuration options. ?options? tpool::post tpoolId Sends a Tcl script to the specified thread pool for execution, returning the ID of the posted job. This command script blocks (entering the event loop to service events) until a worker thread can service the job This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks tpool::wait tpoolId Blocks (entering the event loop to service events) until one or more of the jobs whose IDs are given by the jobList jobList ?varName? argument are completed. Returns a list of completed jobs from jobList. If provided, varName is set to a list of jobs from jobList that are still pending. tpool::get tpoolId Returns the result of the specified jobId. tpool::wait must have reported previously that the job is complete. If no jobId error occurred in the job, the result is the return value of the last command executed in the job script. Any error encountered in job execution is in turn thrown by tpool::get, with the errorCode and errorInfo variables set appropriately. tpool::names Returns a list of existing thread pool IDs. tpool::preserve Increments the reference count of the indicated thread pool. tpoolId tpool::release tpoolId Decrements the reference count of the indicated thread pool. If the reference count is 0 or less, mark the thread pool for termination. The tpool::create command supports several options for configuring thread pools. Table 21-5 lists the available thread pool configuration options. Table 21-5. Thread pool configuration options -minthreads number The minimum number of threads. If the number of live threads in the thread pool is less than this number (including when the thread pool is created initially), new threads are created to bring the number up to the minimum. Default is 0. -maxthreads number The maximum number of threads.When a job is posted to the thread pool, if there are no idle threads and the number of existing worker threads is at the maximum, the thread posting the job blocks (in its event loop) until a worker thread is free to handle the job. Default is 4. -idletime seconds The maximum idle time, in seconds, before a worker thread exits (as long as the number of threads doesn't drop below the -minthreads limit). Default value is 0, meaning idle threads wait forever. -initcmd script A script that newly created worker threads execute. -exitcmd script A script that worker threads execute before exiting. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 22. Tclkit and Starkits Tclkit is a version of the Tcl/Tk interpreter that is designed to make packaging and deployment of Tcl applications easy. Tclkit includes Tcl/Tk, [incr Tcl], the Metakit database, and TclVFS. A Starkit is a special file that contains all the scripts and supporting files you need for your Tcl application. This chapter describes how to package and deploy your application as a Starkit. Tclkit was created by Jean-Claude Wippler as a way to make deploying Tcl applications easier. Tclkit is an extended Tcl interpreter that includes the Metakit database, the [incr Tcl] object-oriented system, and a Virtual File System (VFS). The database is cleverly stored as part of the Tclkit application itself, and the VFS interface is used to make the database look like a private filesystem. Tclkit puts all the scripts normally associated with Tcl and its extensions into this database. The result is a self-contained, single file distribution of Tcl that includes extensions for your GUI, object-oriented programming, a database, and a few other goodies. Metakit is a fast, transactional database with a simple programming API. Like Tcl, Metakit is a compact, efficient library designed to be embedded into applications. The Tcl interface to Metakit gives you a simple, easy way to manipulate persistent data. Although you do not have to program Metakit directly when using Starkits, this Chapter does provide a short introduction to using Metakit to store data for your application. A Starkit is a Metakit database file that stores your application. The VFS interface makes this transparent. Tclkit processes the Starkit just like tclsh or wish, and your application doesn't even have to know it is packaged inside a Starkit. The original Tclkit used an early version of VFS created by Matt Newman. TclVFS was ported to the Tcl core in version 8.4.1 by Vince Darley. Today you can build Tclkit using unmodified Tcl sources. The ActiveTcl distribution includes Metakit, TclVFS and tools to create Starkits, too. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Getting Started with Tclkit Using Tclkit is easy. Just copy the version for your platform (e.g., Linux, Windows or Solaris) into a convenient location under the name tclkit (or tclkit.exe on Windows.) The CD-ROM has builds for lots of platforms, and you can find more at the Tclkit home page: http://www.equi4.com/tclkit You can use the tclkit application just like tclsh. Run with no arguments, it prints a prompt and you can type Tcl commands interactively. If you pass a file argument, then it sources that file just as tclsh would. To use tclkit like wish, you must add this to your scripts: package require Tk Although you can use tclkit to source .tcl files, tclkit is normally used to interpret Starkits, which have a.kit suffix. On UNIX, Starkits use the #! header to associate themselves with tclkit. Make sure thattclkit is in a directory named in yourPATH environment variable. On Windows, you can associate tclkit.exe with the .kit extension. Mac OS X behaves like UNIX (yay!). On Mac Classic systems you can use theFile Source menu to source .kit files. Creating Starkits is described on page 352. Inside a Starkit Tclkit uses the Virtual Filesystem extension to make records in a Metakit database look like files and directories to your application. Through a simple packaging step described shortly, you can easily put all of the Tcl scripts and other supporting files that make up your application into a single database file. The Virtual Filesystem (VFS) extension lets you transparently access these files through the regular file system interface (e.g., open, gets, source, even cd.) A Starkit is a Metakit database that stores an application. The great thing about a Starkit is that it is a single file so it is easy to manage. There is no need to unpack files or run an installer to set things up. Instead, you can distribute your application as two files: the Tclkit interpreter and the Starkit file. Both of these embed a virtual file system that include all the bits and pieces needed for Tcl/Tk and your application. The Tclkit file is platform-specific because it contains Tcl and all the other extensions in a compiled form. There are pre-compiled Tclkits for Windows, Macintosh, and many flavors of Unix. The Starkit file is platform-independent. You can use it with the appropriate Tclkit interpreter on different platforms. Deploying Applications as Starkits The key benefit of Tclkit and Starkits is easy deployment. Users just copy tclkit and your Starkits onto their system; there is no special installation step. You can even have different versions of tclkit and they don't interfere with each other. If users get tired of your application, they just remove the files. Creating Starkits is made easy with the sdx application, which was created by Steve Landers and Jean-Claude Wippler. You organize your collection of application scripts, data files, binary graphics, and online documentation into a file system directory structure. Then you use sdx to wrap that into a Starkit. Creating your own Starkits is described on page 352. You can include binary extensions in a Starkit and dynamically load them. The load command automatically copies the shared library out of the VFS to a temporary location, and loads the library from that location. The temporary file is necessary because the host OS cannot find the library inside the Starkit. Binary extensions make the Starkit platform-specific, but it is possible to put libraries for different platforms into the This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Starkit. For example, the kitten.kit Starkit includes extensions for Windows, Linux, and Solaris. You can combine Tclkit and a Starkit into a Starpack. The advantage of this is that it reduces deployment to a single file. The main drawback is that the Starpack file is relatively large, and it is platform-specific. Use sdx to create Starpacks as described later. The Starkit archive contains a growing collection of Starkits that include applications, games, development tools, a Wiki, tutorials and documentation bundles. There is a copy of the archive on the CD-ROM, and its home page is: http://mini.net/sdarchive/ [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Virtual File Systems The key concept in Tclkit and Starkits is the virtual file system (VFS). You may be familiar with the file system interface inside a Unix operating system that makes everything look the same (files, tape drives, network sockets, pipes). The nice thing about Unix is that a system programmer can use the same APIs to access all of these things. The goal of the Tcl VFS interface is similar in spirit: use the regular Tcl file system interface to make things like embedded databases, FTP servers, and zip files available to the Tcl programmer. The VFS layer in Tcl 8.4 is implemented below the Tcl C APIs for file system access (e.g., Tcl_CreateChannel, Tcl_FSDeleteFile). The result is that scripting commands (e.g., open, file, glob) and any C extensions that use these APIs automatically access any Virtual File Systems that are part of the Starkit. The virtual file system is mounted on a regular file; by default it is mounted on the Starkit. For example, if the Starkit is namedfoo.kit, and its virtual file system contains a file named main.tcl, then it is visible to the Tcl application asfoo.kit/main.tcl. The VFS can contain a whole directory structure (e.g., foo.kit/lib/httpd.tcl or foo.kit/htdocs/help/index.html.) The next section explores some simple Starkits and their file system structure. The main idea is that the Starkit file itself is the root of the virtual file system hierarchy, and everything in the virtual file system is visible to Tcl via the regular scripting commands. If the VFS supports it, you can create and write files as well as read them. Tclkit includes the TclVFS extension that exposes the ability to implement new file systems in Tcl. Ordinarily you do not need to use the vfs API directly when using a Starkit. However, the TclVFS project has created a number of VFS implementations that let you access web sites, FTP sites, zip files, tar files, and more through the filesystem interface. Tclkit does not include all of these, but you can get them as part of the TclVFS extension. Its home page is http://sourceforge.net/projects/tclvfs Accessing a Zip File Through a VFS Tclkit includes a zipvfs package that lets you mount a compressed ZIP file archive and read its contents. This is currently limited to read-only access. Example 22-1 uses the vfs::zip::Mount command to set up the VFS access. If you use other VFS types supplied by the TclVFS extension, you will find that each supplies its own vfs::vfs_type::Mount API: Example 22-1 Accessing a Zip file through a VFS package require vfs::zip => 1.0 # Mount the zip file on "xyz" vfs::zip::Mount c:/downloads/tclhttpd343.zip xyz This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks => filecb15a8 # Examine the contents glob xyz/* => xyz/tclhttpd3.4.3 # Open and read file inside the zip archive set in [open xyz/tclhttpd3.4.3/README] => rechan16 gets $in This HTTPD is written in Tcl and Tk. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Using sdx to Bundle Applications Sdx, which stands for Starkit Developer eXtension, is an application that you run from the Unix, Windows, or MacOS command line to create and manipulate Starkits. It is itself a Starkit, of course. The sdx application is on the CD-ROM, and you can find a link to it from the Starkit home page: http://www.equi4.com/starkit/ Creating a Simple Starkit Creating a Starkit amounts to creating a directory structure that contains the files you need, and then wrapping them up with sdx. Create files under kitname.vfs, and wrap them into thekitname.kit Starkit with: sdx wrap kitname.kit In simple cases, sdx will create the directory structure for you. For example, if you have a self-contained Tcl script calledhello.tcl, then you can turn it into a Starkit like this: sdx qwrap hello.tcl The qwrap operation (i.e., "quick wrap") creates a new Starkit,hello.kit, that includes the original hello.tcl script organized into a virtual file system hierarchy with some additional support files. You run the Starkit like this: tclkit hello.kit On Unix systems you can also execute the Starkit directly. The file uses the #! syntax to specify that tclkit should run the file. On Windows, you can achieve the same effect by associating tclkit.exe with files that end in.kit. Examining a Starkit There are two ways to look at a Starkit. You can get a listing of the files with the sdx lsk operation, or you can use sdx unwrap to extract the files from the Starkit into a kitname.vfs directory. Example 22-2 shows the lsk output for hello.kit. The dates are inYY/MM/DD format: Example 22-2 The output of sdx lsk hello.kit This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks hello.kit: dir lib/ 67 02/11/08 12:07 main.tcl hello.kit/lib: dir app-hello/ hello.kit/lib/app-hello: 43 02/11/08 12:10 hello.tcl 72 02/11/08 12:07 pkgIndex.tcl Standard Package Organization The qwrap operation turns the hello.tcl script into the app-hello package. If necessary, sdx adds a package provide app-hello 1.0 command to the hello.tcl script. It also creates a shortmain.tcl script that initializes the Starkit system and invokeshello.tcl by doing apackage require. Example 22-3 shows main.tcl: Example 22-3 The main program of a Starkit package require starkit starkit::startup package require app-hello When you run the Starkit, its Metakit database is mounted into a Virtual File System that is visible to the Tcl application. Tclkit sources the main.tcl script it finds in the VFS. The starkit::startup procedure updates the auto_path to contain the Starkit's lib directory, so any packages stored there are available to the package mechanism. By convention, the application is put into a package with the name app-kitname. Example 22-4 shows the pkgIndex.tcl, which causes thepackage require app-hello command to source hello.tcl. Example 22-4 The pkgIndex.tcl in a Starkit package ifneeded app-hello 1.0 \ [list source [file join $dir hello.tcl]] The dir variable is set by the package mechanism to be the directory containing thepkgIndex.tcl file. That the lib directory happens to be inside the virtual file system is completely transparent to the package mechanism. The package mechanism is described in more detail in Chapter 12. Creating a Starpack This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A Starpack contains a copy of Tclkit and your Starkit. Use sdx to create Starpacks. The -runtime flag specifies which Tclkit application you want to merge with your Starkit. For example, to build a Windows Starpack out of our hello.tcl application: sdx wrap hello.kit -runtime tclkit-win32.exe To build a Starkit for Linux, use the appropriate runtime: sdx wrap hello.kit -runtime tclkit-linux-x86 There are 4 variations of the Windows Tclkit. One option uses zlib to automatically compress Tclkit and the Metakit database. These have .upx in their name. The other creates a console-mode application that does not include Tk. These have -sh in their name. The smallest Tclkit, tclkit-win32-sh.upx.exe, is only 450 K. Eventclkit-win32.upx.exe is only 907 K, so you really can create complete applications that fit easily onto a floppy disk! The auto-compress variation is also available on the Linux x86 builds as the tclkit-linux-x86.upx.bin runtime file. Check the Tclkit home page for the latest set of Tclkit builds: http://www.equi4.com/tclkit [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Exploring the Virtual File System in a Starkit Example 22-2 introduces the standard, recommended VFS structure for a Starkit that makes everything into a package, even the main application. However, in this section we are going to show a Starkit without packages in order to get a feel for how the VFS works. For example, instead of doing the package require hello, the main.tcl script of Example 22-3 could source the hello.tcl file directly: source hello.kit/lib/app-hello/hello.tcl However, this only works if you are in the directory containing the hello.kit file. Use starkit::topdir to find things in the Starkit Virtual File System. The starkit::topdir variable is set by starkit::startup to be the file name of the Starkit, which is also the root of the Virtual File System inside the Starkit. The value of starkit::topdir is an absolute pathname, so it is always valid.Example 22-5 shows a Starkit that manipulates its virtual file system. Example 22-5 A Starkit that examines its Virtual File System package require starkit starkit::startup puts "Contents of VFS before" foreach f [glob [file join $starkit::topdir *]] { puts "[file size $f] $f" } puts "Reading data file" set in [open [file join starkit::topdir data]] set X [read $in] puts $X close $in set out [open [file join $starkit::topdir data.new w]] puts $out $X close $out puts "Contents of VFS after" foreach f [glob [file join $starkit::topdir *]] { puts "[file size $f] $f" } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Create the Starkit by putting the code in Example 22-5 into a file namedmain.tcl in the write.vfs directory. Then use sdx as shown in Example 22-6: Example 22-6 Creating a simple Starkit # These are UNIX shell commands mkdir write.vfs cp 22_5.tcl write.vfs/main.tcl sdx wrap write.kit tclkit write.kit If you run the write.kit file more than once you will notice that thewrite.kit/data.new file does not persist between runs. This is because, by default, the Metakit database is modified in main memory and it is not written out to the Starkit file. If you want to store files long term, use the -writable flag to sdx: sdx wrap write.kit -writable [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Creating tclhttpd.kit The Tcl Web Server, TclHttpd, has its source tree organized so you can run the server without any installation steps. This makes it very easy to put into a Starkit. For our first version, which we will refine later, all we need is a copy of the TclHttpd source code and a copy of the Standard Tcl Library, tcllib. I used the tcllib1.3 directory that was installed in the main lib directory of my desktop Tcl environment, and the tclhttpd3.4.3 source distribution. Example 22-7 shows the contents of the tclhttpd.vfs directory: Example 22-7 The contents of the tclhttpd.vfs directory, version 1 main.tcl tclhttpd3.4.3/bin/httpd.tcl tclhttpd3.4.3/bin/httpdthread.tcl tclhttpd3.4.3/bin/tclhttpd.rc tclhttpd3.4.3/lib/ (lots of files) tclhttpd3.4.3/htdocs/ (lots of files) tcllib1.3 (copy of /usr/local/lib/tclib1.3) Example 22-8 shows the short main.tcl script used to start up the Starkit. The first two lines are common to all Starkits. Thestarkit::autoextend command is used to add the tcllib1.3 directory to the auto_path so the Standard Tcl Library packages are available. The last line uses starkit::topdir to find the TclHttpd startup script, bin/httpd.tcl. Example 22-8 The main program for the TclHttpd Starkit, version 1 package require starkit starkit::startup starkit::autoextend [file join $starkit::topdir tcllib1.3] source [file join $starkit::topdir tclhttpd3.4.3/bin/httpd.tcl] The Starkit is created and used as shown below, assuming tclhttpd.vfs is in the current directory. Note that command line options are passed through, so you can also use this Starkit to host an htdocs directory outside the Starkit. If you don't specify one, thehtdocs tree inside the Starkit is used: sdx wrap tclhttpd.kit tclkit tclhttpd.kit -port 8080 -docRoot /my/htdocs The standard structure introduced in Example 22-2 organizes packages under a lib directory. By convention, the version numbers are dropped from the package directory names. Because everything is self contained, there really isn't any need to have explicit version numbers in the directory names. The file system for the second version of tclhttpd.kit is shown in Example 22-9. Example 22-9 Contents of the tclhttpd.vfs directory, version 2 This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks main.tcl bin/httpd.tcl bin/httpdthread.tcl bin/tclhttpd.rc lib/tclhttpd/pkgIndex.tcl lib/tclhttpd/*.tcl (lots of files) lib/tcllib/pkgIndex.tcl lib/tcllib/* (lots of subdirectories) The main.tcl file is shown in Example 22-10. There is no need to adjust the auto_path because starkit::startup ensures that the lib directory is on it. Example 22-10 The main program for the TclHttpd Starkit, version 2 package require starkit starkit::startup source [file join $starkit::topdir bin/httpd.tcl] One of the first things I noticed about the tclhttpd.vfs was that tcllib took up far more space than the rest of TclHttpd. TclHttpd only uses a few of the many modules in tcllib. I ended up only adding the modules I needed in order to keep the Starkit smaller. Another way to solve this problem is to use the tcllib.kit Starkit that can be shared among applications. Creating shared Starkits is the topic of the next section. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Creating a Shared Starkit Starkits can be used to create modules that are shared by other applications. For example, the kitten.kit Starkit contains about 50 popular extensions, and several of them are binary extensions. It is over 4 MB in size, and so it is a great candidate for sharing. You can find kitten.kit on the CD-ROM or in the Starkit archive. By organizing each shared module into a Starkit with the appropriate structure, it is a simple matter to share them. Whenever a Starkit is sourced, Tclkit mounts its VFS and looks for itsmain.tcl file. This is true for shared Starkits as well as the main Starkit of an application. If main.tcl calls starkit::startup, then the lib directory in the VFS is automatically added to theauto_path. Any libraries organized under lib will be automatically accessible to the application that sourced the Starkit. You can add a little logic to make your package behave differently if it is run as the main Starkit or sourced into another application. For example, this is done in the tcllib Starkit, which starts a stand-alone Wiki that describes the Standard Tcl Library APIs if run as its own Starkit. Otherwise it just sets up tcllib to be shared by the main application.Example 22-11 shows the main.tcl of tcllib.kit. It has to explicitly add the tcllib directory to the auto_path because it has both alib and tcllib directory in its VFS: Example 22-11 The Standard Tcl Library Starkit main.tcl file package require starkit if {[starkit::startup] eq "starkit"} { # Do application startup package require app-tcllib } else { # Set up to be used as a library set vfsroot [file dirname [file normalize [info script]]] lappend auto_path [file join $vfsroot tcllib] } Another side effect of starkit::startup is to set starkit::topdir. However, this variable is only set once. If you source other Starkits that call starkit::startup, then the starkit::topdir value is not disturbed. This behavior changed in Tclkit 8.4.2. In earlier versions, starkit::topdir was set by each Starkit, so you had to worry about saving its value if you loaded other Starkits. If you source tcllib.kit and cannot package require its packages, check its main.tcl. If it uses starkit::topdir in the non-Starkit case, then it is an older version. Simply unwrap it, make its main.tcl look like Example 22-11, and wrap it back up to fix the problem. The starkit::startup procedure determines the environment of the application by making a series of tests against the script environment. Its return value helps your main.tcl script distinguish between starting out as the main Starkit, or being loaded into another Starkit as a library. Table 22-1 lists the return values of the starkit::startup procedure in the order they are checked: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 22-1. Return values of the starkit::startup procedure starpack The Starkit was bundled with tclkit to make a Starpack. starkit The Starkit was run by itself. unwrapped The Starkit was run out of its unpacked vfs directory. tclhttpd The Starkit was sourced into TclHttpd. plugin The Starkit was sourced in the browser plugin. service The Starkit was run in an NT service. sourced The Starkit was sourced by another Starkit. The easiest way to organize your shared Starkits is to put them into the same directory. Example 22-12 shows how the TclHttpd Starkit is modified to load the tcllib Starkit from the same directory. Example 22-12 The main program for TclHttpd Starkit, version 3 package require starkit starkit::startup set dir [file dirname $starkit::topdir] if {![file exists [file join $dir tcllib.kit]]} { puts stderr "Please install tcllib.kit in $dir" exit 1 } source [file join $dir tcllib.kit] source [file join $starkit::topdir tclhttpd/bin/httpd.tcl] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Metakit This section provides a short overview of the Metakit database that is used by Starkits to store their data. You do not need to program Metakit directly to use Starkits because of the transparent VFS interface. However, Metakit is an easy-to-use database that provides more power than storing data in flat files, but not as much power (or overhead) as a full SQL database engine. Metakit has a simple, flexible programming API and an efficient implementation. By storing your application data in a Metakit table, you can have persistent data that lives with your application. You can store the data in a file separate from your application, or right inside the application Starkit itself. This Chapter gives a few introductory examples and explains some of the other features that are available. This Chapter does not provide a complete reference. The following URLs are excellent guides to the Tcl interface for Metakit. The first URL is also on the CD as sdarchive/doc/mk4dok.kit. http://www.equi4.com/metakit/tcl.html http://www.equi4.com/metakit/wiki.cgi/mk4tcl http://www.markroseman.com/tcl/mktcl.html Metakit Data Model The Metakit data model is table-oriented. A view is like a table with rows of values. Each row in a view has anindex, which is an integer that counts from 0. The elements (i.e., columns or fields) of a row are called properties. A property might itself be a view, which leads to nested views (i.e., nested tables). All the rows in a view have the same properties, and the properties of a view can be changed dynamically. You can directly relate (view, row, property) to (table, row, field) when thinking about Metakit views. A Metakit data file has one or more views within it. When you open a Metakit file, you specify a tag. Views are specified astag.view. Row N of a view is specified as tag.view!N. Such a position within a view is called acursor, and there are operations to create cursor variables and move them through a view. If a property is a nested view, then you can specify a row in the nested view with tag.view!N.subview!M. Examining a Metakit Database Our first exercise is to open up a Starkit and look at the Metakit database views inside. The mk::file command implements several operations. The open operation opens a database and associates it with a tag. Theviews operation lists the views in the database identified by the tag. The close operation commits any outstanding modifications to the database. The othermk::file operations are used to control the commit This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks behavior and to save or restore the database to an external file. Example 22-13 illustrates how to open a Metakit database and examine the views it contains: Example 22-13 Examining the views in a Metakit database package require Mk4tcl => 2.4.8 mk::file open tclhttpd tclhttpd.kit => tclhttpd mk::file views tclhttpd => dirs The mk::view command has several operations to inspect and manipulate views. Thelayout operation queries or sets the properties of a view. Given only a view, the layout operation returns the properties defined for the view. Each property has a type, and nested views are represented as a nested list of the property name and its list of properties. Given a set of properties, the layout operation defines new properties for a view. This may involve adding or deleting properties from any existing rows in the table. Example 22-14 shows the layout of the dirs view in a Starkit. The files property is a nested view, which provides a natural way to represent a hierarchical filesystem. The example gets the name property of tclhttpd.dirs!0.files!0, which is the first file in the first directory in the view: Example 22-14 Examining data in a Metakit view mk::view layout tclhttpd.dirs => name parent:I {files {name size:I date:I contents:B}} mk::view size tclhttpd.dirs => 48 mk::get tclhttpd.dirs!0 => name <root> parent -1 mk::get tclhttpd.dirs!1 => name tcllib1.3 parent 0 mk::get tclhttpd.dirs!1 name => tcllib1.3 mk::get tclhttpd.dirs!0.files!0 name => main.tcl Of course, real applications will want to query views for values that have certain properties. The mk::select command returns the row numbers for rows that match given criteria, or all the row numbers if no matching criteria are given. You can match on multiple properties, and there are flags that control how the match is done. For example, you can do numeric comparisons, regular expression or glob matches, and min/max comparisons. Example 22-15 shows two forms of mk::select. The KitWalk procedure enumerates the files in a given directory, which is the view $tag.dirs!$dir.files. Then it queries the row indices for the$tag.dirs view whose parent property equals $dir, and calls itself recursively to process the child directories. KitWalk provides a similar function to sdx lsk: Example 22-15 Selecting data with mk::select This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks proc KitWalk {tag dir {indent 0}} { set prefix [string repeat " " $indent] puts "$prefix[mk::get $tag.dirs!$dir name]/" incr indent 2 # List the plain files in the directory, if any foreach j [mk::select $tag.dirs!$dir.files] { puts "$prefix [mk::get $tag.dirs!$dir.files!$j name]" } # Recursively process directories where $dir is the parent foreach i [mk::select $tag.dirs parent $dir] { KitWalk $tag $i $indent } } proc KitInit {starkit} { mk::file open starkit $starkit if {[mk::file views starkit] != "dirs"} { mk::file close $starkit error "This database is not a starkit" } return starkit ;# db tag } proc KitTest {} { set tag [KitInit tclhttpd.kit] KitWalk $tag 0 } Creating a Metakit View Creating a new view is simple. Example 22-16 opens a database file mydb.tkd and creates a viewtest with three properties: name, blob, and i. If the file does not exist, then it gets created automatically. If the test view doesn't exist, it gets created. If it already exists, it is reformatted to have the new properties. The name property has the default type, which is a null-terminated string. Theblob property is a binary value (B) which can store anything, including null characters. The i property is a 32-bit integer (I). Other types include 64-bit integer (L), 32-bit floating point (F), 64-bit double-precision floating point (D), and null-terminated string (S), which is the default and needn't be specified. Example 22-16 Creating a new view mk::file open mydb mydb.tkd => mydb mk::view layout mydb.test {name blob:B i:I} => mydb.test mk::file close mydb The mk::set command sets property values, and themk::row command modifies rows. Example 22-17 adds a few values to thetest view. Note that you can insert into rows beyond the end of the view and it is automatically extended. If you only define some properties for a row, the This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks other properties get default values. Other mk::row operations include insert, replace, and delete. Example 22-17 Adding data to a view mk::set mydb.test!0 name hello => mydb.test!0 mk::get mydb.test!0 => hello {} 0 mk::row append mydb.test "line two" 0x0 65 => mydb.test!1 mk::view size mydb.test => 2 mk::set mydb.test!100 i 1234 => mydb.test!100 mk::view size mydb.test => 101 Storing Application Data in a Starkit Your application can create new views in a Starkit to store persistent data. Remember to wrap your application with the -writable flag. You can determine the name of the Starkit from $starkit::topdir, and then define a new view within it. Of course, remember that Starkits usedirs view to store files, but you can create any number of other views within your Starkits. This is illustrated in Example 22-18, which records each time the application was run in a simple audit view. Example 22-18 is careful to find the existing Metakit handle that is already opened by Tclkit. Thevfs::filesystem info command returns an alternating list of VFS names and their Metakit database handle. The example extracts the handle and saves it in the $db variable. This is important because opening the same Metakit file twice (for writing) can cause corruption: Example 22-18 Storing data in a Starkit package require starkit starkit::startup set db [lindex [vfs::filesystem info [$starkit::topdir]] 1] mk::view layout $db.audit {action timestamp:I} mk::row append $db.audit "Run as pid [pid]" [clock seconds] puts "$argv0 has been run [mk::view size $db.audit] times" To test this, put this example into the main.tcl of a trivial Starkit. When you create the Starkit, remember the-writable option with sdx: mkdir bundle.vfs cp 22_18.tcl bundle.vfs/main.tcl sdx wrap bundle.kit -writable- This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Wikit and the Tcler's Wiki The alternative to storing data in the Starkit file is to have a separate Metakit data file. This is the approach taken by Wikit. The wikit.kit file is the Wikit application, and the wikit.tkd file is a Metakit database file that stores all the pages in the Wiki. (Creating a new Wiki is simple, just specify a different .tkd file name.) The advantage of having a separate Metakit file is that you can easily maintain your application by unwrapping and wrapping your application Starkit. Otherwise, if you put the application data directly into the Starkit you have to extract it and restore it as an additional maintenance step. In that case, you must use the mk::file save and load operations to save and restore your Metakit views to a file. A Wiki is a web site that users can easily edit using a simplified markup syntax. Wikit is a Wiki implementation in Tcl using Metakit to store pages. It can run as a stand-alone Tk application, a GGI script, as its own little web server, or embedded into another application as a documentation bundle. There is a copy of wikit.tkd on the CD-ROM. For example, you run a stand alone copy of the Tcler's Wiki as: tclkit wikit.kit wikit.tkd [*] The live Wiki is at wiki.tcl.tk , and you can find out more about Wikit at: [*] http://wiki.tcl.tk is an alias for http://mini.net/tcl. http://wiki.tcl.tk/wikit [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks More Ideas This Chapter has provided a brief introduction to Tclkit, Starkits, and Metakit. This should be enough to help you get started creating your own Starkits and using Metakit for persistent storage. You should consult the documentation on the Web for more detailed reference material. Document Bundles The Starkit archive includes a number of documentation bundles. For example, mk4dok.kit is a Starkit that contains all the MetaKit documentation. These document bundles are all based on Wikit. It is very easy to create Wiki-style documentation for your application and then bundle it up as a Metakit file. You can load wikit.kit and your .tkd document bundle into your application and use the "local" Wikit interface to display your documentation. For example, the critcl Starkit displays its help with this simple command: Wikit::init [file join $::starkit::topdir doc critcl.tkd] Self-Updating Applications The client in a client-server application is an ideal candidate for a self-updating application. The front-end client is a Starkit with some simple startup logic that connects to a server via HTTP and displays a pretty splash screen. The server, which is often based on TclHttpd, delivers code updates to the client. The client caches the code in the VFS inside the Starkit. The application is maintained on the server, and clients automatically get updated as they are used. This scenario has the same deployment advantage as browser-based applications: you deploy a "thin-client" to desktops that rarely, if ever, changes and you update the application code on the server. In addition, this application structure lets you create a nice client front-end that uses Tcl/Tk instead of HTML, yet still have the benefit of an easy to manage server-side installation of the application code. This design pattern is being used for a number of large-scale commercial application deployments with considerable success. A similar system is used with the Starkit archive. If you do: sdx update tclhttpd.kit The sdx application contacts the web server running the archive and checks for any updates available for the Starkit. Only the differences are transmitted, so updates are quick, and they are automatically applied to your copy of the Starkit. This should work for all the Starkits in the snapshot of the archive on the CD-ROM. Simple Installers This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks In some cases you simply must install a collection of files as part of your application. It is very easy to include those files in the VFS, and then extract them into the local file system the first time your application runs. Or, you can create a traditional "installer" that unpacks the entire application from the Starkit (or Starpack). [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Part III: Tk Basics Part III introduces Tk, the toolkit for building graphical user interfaces. The Tcl command interface to Tk makes it quick and easy to build powerful user interfaces. Tk is portable and your user interface code can work unchanged on UNIX, Windows, and the Macintosh. Chapter 23 describes the basic concepts of Tk and provides an overview of its facilities. Chapter 24 illustrates Tk with three example programs including a browser for the examples from this book. These examples use facilities that are described in more detail in later chapters. Geometry managers implement the layout of a user interface. Chapters Chapter 25, Chapter 26, and Chapter 27 describe the pack, grid, and place geometry managers. The packer and gridder are general-purpose managers that use constraints to create flexible layouts with a small amount of code. The placer is a special purpose geometry manager that can be used for special effects. Chapter 28 describes the panedwindow widget, which is also a geometry manager. Chapter 29 describes event bindings that associate Tcl commands with events like keystrokes and mouse motion. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Chapter 23. Tk Fundamentals This chapter introduces the basic concepts used in the Tk graphical user interface toolkit. Tk adds about 45 Tcl commands that let you create and manipulate widgets in a graphical user interface. Tk works with the X window system, Windows, and Macintosh. The same script can run unchanged on all of these major platforms. Tk is a toolkit for programming graphical user interfaces. It was designed for the X window system used on UNIX systems, and it was ported later to the Macintosh and Windows environments. Tk shares many concepts with other windowing toolkits, but you do not need to know much about graphical user interfaces to get started with Tk. Tk provides a set of Tcl commands that create and manipulate widgets. A widget is a window in a graphical user interface that has a particular appearance and behavior. The terms widget and window are often used interchangeably. Widget types include buttons, scrollbars, menus, and text windows. Tk also has a general-purpose drawing widget called a canvas that lets you create lighter-weight items such as lines, boxes, and bitmaps. The canvas is extremely powerful, yet very easy to use. The Tcl commands added by Tk are summarized at the end of this chapter. Tk widgets are organized in a hierarchy. To an application, the window hierarchy means that there is a primary window, and inside that window there can be a number of children windows. The children windows can contain more windows, and so on. Just as a hierarchical file system has directories (i.e., folders) that are containers for files and directories, a hierarchical window system uses windows as containers for other windows. The hierarchy affects the naming scheme used for Tk widgets as described later, and it is used to help arrange widgets on the screen. Widgets are under the control of a geometry manager that controls their size and location on the screen. Until a geometry manager learns about a widget, it will not be mapped onto the screen and you will not see it. Tk has powerful geometry managers that make it very easy to create nice screen layouts. The main trick with any geometry manager is that you use frame widgets as containers for other widgets. One or more widgets are created and then arranged in a frame by a geometry manager. By putting frames within frames you can create complex layouts. There are three different geometry managers you can use in Tk: grid, pack, and place, and one widget, the panedwindow, that also acts as a geometry manager. The Tk geometry managers are discussed in detail in Chapters 25, 26, and 27; the panedwindow is discussed in Chapter 28. A Tk-based application has an event-driven control flow, like most window system toolkits. The Tk widgets handle most events automatically, so programming your application remains simple. For specialized behaviors, you use the bind command to register a Tcl command that runs when an event occurs. There are lots of events, including mouse motion, keystrokes, window resize, and window destruction. You can also define virtual events, like Cut and Paste, that are caused by different events on different platforms. Bindings are discussed in detail in Chapter 29. Chapter 16 describes I/O events and the Tcl event loop, whileChapter 50 describes C programming and the event loop. Event bindings are grouped into classes, which are called bindtags. The bindtags command associates a widget with an ordered set of bindtags. The level of indirection between the event bindings and the widgets creates a flexible and powerful system for managing events. You can create your own bindtags and dynamically change the bindtags for a widget to support mode changes in your application. A concept related to binding is focus. At any given time, one of the widgets has the input focus, and keyboard events are directed to it. There are two general approaches to focusing: give focus to the widget under the mouse, or explicitly set the focus to a particular widget. Tk provides commands to change focus so you can implement either style of focus management. To support modal dialog boxes, you can forcibly grab the focus away from other widgets.Chapter 39 describes focus, grabs, and dialogs. The basic structure of a Tk script begins by creating widgets and arranging them with a geometry manager, and then binding actions to the widgets. After the interpreter processes the commands that initialize the user interface, the event loop is entered and your application begins This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks running. If you use wish interactively, it creates and displays an empty main window and gives you a command-line prompt. With this interface, your keyboard commands are handled by the event loop, so you can build your Tk interface gradually. As we will see, you will be able to change virtually all aspects of your application interactively. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Hello, World! in Tk Our first Tk script is very simple. It creates a button that prints "Hello, World!" to standard output when you press it. Above the button widget is a title bar that is provided by the window manager, which in this case is twm under X windows: Example 23-1 "Hello, World!" Tk program #!/usr/local/bin/wish button .hello -text Hello \ -command {puts stdout "Hello, World!"} pack .hello -padx 20 -pady 10 The first line identifies the interpreter for the script: #!/usr/local/bin/wish This special line is necessary if the script is in a file that will be used like other UNIX command files. Chapter 2 describes how to set up scripts on different platforms. There are two Tcl commands in the script: one to create the button, and one to make it visible on the display. The button command creates an instance of a button: button .hello -text Hello \ -command {puts stdout "Hello, World!"} => .hello The name of the button is .hello. The label on the button is Hello, and the command associated with the button is: puts stdout "Hello, World!" The pack command maps the button onto the screen. Some padding parameters are supplied, so there is space around the button: pack .hello -padx 20 -pady 10 If you type these two commands into wish, you will not see anything happen when thebutton command is given. After the pack command, though, you will see the empty main window shrink to be just big enough to contain the button and its padding. The behavior of the packer will be discussed further in Chapters 24 and 25. Tk uses an object-based system for creating and naming widgets. Associated with each class of widget (e.g., Button) is a command that This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks creates instances of that class of widget. As the widget is created, a new Tcl command is defined that operates on that instance of the widget. Example 23-1 creates a button named.hello, and we can operate on the button using its name as a Tcl command. For example, we can cause the button to highlight a few times: .hello flash Or we can run the command associated with the button: .hello invoke => Hello, World! Tk has widget classes and instances, but it is not fully object oriented. It is not possible to subclass a widget class and use inheritance. Instead, Tk provides very flexible widgets that can be configured in many different ways to tune their appearance. The resource database can store configuration information that is shared by many widgets, and new classes can be introduced to group resources. Widget behavior is shared by using binding tags that group bindings. Instead of building class hierarchies, Tk uses composition to assemble widgets with shared behavior and attributes. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Naming Tk Widgets The period in the name of the button instance, .hello, is required. Tk uses a naming system for the widgets that reflects their position in a hierarchy of widgets. The root of the hierarchy is the main window of the application, and its name is simply a dot (i.e., .). This is similar to the naming convention for directories in UNIX where the root directory is named /, and then / is used to separate components of a file name. Tk uses a dot in the same way. Each widget that is a child of the main window is named something like .foo. A child widget of .foo would be .foo.bar, and so on. Just as file systems have directories that are containers for files and other directories, the Tk window hierarchy uses frame widgets that are containers for widgets and other frames. Each component of a Tk pathname must start with a lowercase letter or a number. Obviously, a component cannot include a period, either. The lower case restriction avoids a conflict with resource class names that begin with an upper case letter. A resource name can include Tk pathname components and Tk widget classes, and case is used to distinguish them. Chapter 31 describes resources in detail. Store widget names in variables. There is one drawback to the Tk widget naming system. If your interface changes enough it can result in some widgets changing their position in the widget hierarchy. In that case they may need to change their name. You can insulate yourself from this programming nuisance by using variables to hold the names of important widgets. Use a variable reference instead of widget pathnames in case you need to change things, or if you want to reuse your code in a different interface. The widget creating commands return the name of the widget: set b [button .hello -text "Hello" -command {puts "Hello!"}] You use $b as a command to operate on the button: $b configure -background green [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Configuring Tk Widgets Example 23-1 illustrates a style of named parameter passing that is prevalent in the Tk commands. Pairs of arguments specify the attributes of a widget. The attribute names begin with -, such as -text, and the next argument is the value of that attribute. Even the simplest Tk widget can have a dozen or more attributes that can be specified this way, and complex widgets can have 30 or more attributes. However, the beauty of Tk is that you need to specify only the attributes for which the default value is not good enough. This is illustrated by the simplicity of the Hello, World example. Finally, each widget instance supports a configure operation, which can be abbreviated to config, that can query and change these attributes. The syntax for config uses the same named argument pairs used when you create the widget. For example, we can change the background color of the button to red even after it has been created and mapped onto the screen: .hello config -background red Widget attributes can be redefined any time, even the text and command that were set when the button was created. The following command changes .hello into a goodbye button: .hello config -text Goodbye! -command exit Widgets have a cget operation to query the current value of an attribute: .hello cget -background => red You can find out more details about a widget attribute by using configure without a value: .hello config -background => -background background Background #ffe4c4 red The returned information includes the command-line switch, the resource name, the class name, the default value, and the current value, which is last. The class and resource name have to do with the resource mechanism described in Chapter 31. If you only specify configure and no attribute, then a list of the configuration information for all widget attributes is returned. Example 23-2 uses this to print out all the information about a widget: Example 23-2 Looking at all widget attributes proc Widget_Attributes {w {out stdout}} { puts $out [format "%-20s %-10s %s" Attribute Default Value] foreach item [$w configure] { puts $out [format "%-20s %-10s %s" \ [lindex $item 0] [lindex $item 3] \ [lindex $item 4]] } } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Tk Widget Attributes and the Resource Database A widget attribute can be named three different ways: by its command-line option, by its resource name, and by its resource class. The command-line option is the format you use in Tcl scripts. This form is always all lowercase and prefixed with a hyphen (e.g., -offvalue). The resource name for the attribute has no leading hyphen, and it has uppercase letters at internal word boundaries (e.g., offValue). The resource class begins with an uppercase letter and has uppercase letters at internal word boundaries. (e.g., OffValue). The tables in this book list widget attributes by their resource name. You need to know these naming conventions if you specify widget attributes via the resource mechanism. The command-line option can be derived from the resource name by mapping it to all lowercase. The primary advantage of using resources to specify attributes is that you do not have to litter your code with attribute specifications. With just a few resource database entries you can specify attributes for all your widgets. In addition, if attributes are specified with resources, users can provide alternate resource specifications in order to override the values supplied by the application. For attributes like colors and fonts, this feature can be important to users. Resource specifications are described in detail in Chapter 31. The Tk Manual Pages This book provides summaries for all the Tk commands, the widget attributes, and the default bindings. However, for the absolute truth, you may need to read the on-line manual pages that come with Tk. They provide a complete reference source for the Tk commands. You should be able to use the UNIX man program to read them: % man button The tkman program provides a very nice graphical user interface to the UNIX manual pages. On the Macintosh platform, the manual pages are formatted into HTML documents that you can find in the HTML Docs folder of the Tcl/Tk distribution. On Windows, the manual pages are formatted into Help documents. You can find the manual pages on the web at: http://www.tcl.tk/man/ There are a large number of attributes that are common across most of the Tk widgets. These are described in a separate man page under the name options. Each man page begins with aSTANDARD OPTIONS section that lists which of these standard attributes apply, but you This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks have to look at the options man page for the description. In contrast, the tables in this book always list all widget attributes. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Summary of the Tk Commands The following tables list the Tcl commands added by Tk. The page number in the table is the primary reference for the command, and there are other references in the index. Widget Commands Table 23-1 lists commands that create widgets. There are 18 different widgets in Tk, although 4 of them are variations on a button, and 5 are devoted to different flavors of text display. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 23-1. Tk widget-creation commands Command Pg. Description button 454 Create a command button. canvas 557 Create a canvas, which supports lines, boxes, bitmaps, images, arcs, text, polygons, and embedded widgets. checkbutton 458 Create a toggle button that is linked to a Tcl variable. entry 507 Create a one-line text entry widget. frame 485 Create a container widget used with geometry managers. label 490 Create a read-only, multiline text label. labelframe 485 Create a container widget used with geometry managers that has extra label attributes. (Tk 8.4) listbox 519 Create a line-oriented, scrolling text widget. menu 462 Create a menu. menubutton 462 Create a button that posts a menu. message 493 Create a read-only, multiline text message. panedwindow 429 Create a container widget that controls other widgets in a paned fashion. (Tk 8.4) radiobutton 458 Create one of a set of radio buttons linked to one variable. scale 495 Create a scale widget that adjusts the value of a variable. scrollbar 499 Create a scrollbar that can be linked to another widget. spinbox 511 Create a spinbox widget that is a composite entry widget with button controls for adjusting the value. (Tk 8.4) text 531 Create a general-purpose, editable text widget. toplevel 485 Create a frame that is a new top level window. Widget Manipulation Commands Table 23-2 lists commands that manipulate widgets and provide associated functions like input focus, event binding, and geometry management. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 23-2. Tk widget-manipulation commands Command Pg. Description bell 497 Ring the terminal bell device. bind 435 Bind a Tcl command to an event. bindtags 437 Create binding classes and control binding inheritance. clipboard 594 Manipulate the clipboard. destroy 605 Delete a widget. event 446 Define and generate virtual events. focus 603 Control the input focus. font 641 Set and query font attributes and measurements. grab 604 Steal the input focus from other widgets. grid 419 Arrange widgets into a grid with constraints. image 626 Create and manipulate images. lower 409 Lower a window in the stacking order. option 477 Set and query the resources database. pack 409 Pack a widget in the display with constraints. place 427 Place a widget in the display with positions. raise 409 Raise a window in the stacking order. selection 593 Manipulate the selection. send 648 Send a Tcl command to another Tk application. tk 669 Query or set the application name or global caret. tkerror 202 Handler for background errors. tkwait 605 Wait for an event. update 608 Update the display by going through the event loop. winfo 663 Query window state. wm 657 Interact with the window manager. Support Procedures Table 23-3 lists several support procedures that implement standard dialogs, option menus, and other facilities. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 23-3. Tk support procedures Command Pg. Description tk_bisque 621 Install bisque family of colors. tk_chooseColor 602 Dialog to select a color. (Tk 4.2) tk_chooseDirectory 600 Dialog to select a directory. (Tk 8.2) tk_dialog 599 Create simple dialogs. tk_focusFollowsMouse 603 Install mouse-tracking focus model. tk_focusNext 604 Focus on next widget in tab order. tk_focusPrev 604 Focus on previous widget in tab order. tk_getOpenFile 600 Dialog to open an existing file. (Tk 4.2) tk_getSaveFile 600 Dialog to open a new file. (Tk 4.2) tk_messageBox 600 Message dialog. (Tk 4.2) tk_optionMenu 465 Create an option menu. tk_popup 465 Create a pop-up menu. tk_setPalette 621 Set the standard color palette. (Tk 4.2) [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Other Widget Sets This book describes the set of widgets provided by core Tk distribution. There are number of other widget sets for Tk. Some are implemented as Tcl procedures that compose the basic widgets into useful combinations (e.g., BWidgets). Others are C-based toolkits (e.g., Tix and BLT). A few of the more popular widget sets are listed here: BLT George Howlett created BLT. It includes a great graph widget that efficiently supports large datasets. It also includes a tabbed notebook and tree view widget. Its busy widget covers your application with a transparent widget that just displays a watch cursor, which is handy when the application is busy doing something and you don't want to accept mouse clicks. This is a C-based toolkit. http://www.sourceforge.net/projects/blt/ Tix Tix was created by Ioi Lam, and is now supported by a team of volunteers. It includes several widgets and an infrastructure for creating new widgets in Tcl. Notable features include balloon help, tabbed windows, paned window, and a hierarchy browser. This is a C-based toolkit, although it includes a number of compound widgets created in Tcl. http://tix.sourceforge.net/ [incr Tk] and [incr Widgets] [incr Tk] is a C-based framework for creating compound widgets using the [incr Tcl] object system. [incr Widgets] is the widget set created using that framework. It includes loads of widgets, from simple labeled-entry widgets up through HTML display widgets. These tools are described in Chad Smith's book, [incr Tcl] from the Ground Up (Osborne-McGraw Hill, 1999). http://incrtcl.sourceforge.net This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks BWidgets BWidgets is a set of Tcl-based widgets. It includes a variety of compound widgets, including a tabbed notebook, combobox, and hierarchy browser. It is hosted at the Standard Tcl Lib (tcllib) web site: http://www.sourceforge.net/projects/tcllib TkTable TkTable is combination of a gridding geometry manager and several text-oriented widgets. It makes it easy to lay out tabular data like spreadsheets, and it also provides a large amount of control over the formatting of cells and their data. http://www.sourceforge.net/projects/tktable [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 24. Tk by Example This chapter introduces Tk through a series of short examples. The ExecLog runs a program in the background and displays its output. The Example Browser displays the Tcl examples from the book. The Tcl Shell lets you type Tcl commands and execute them in a slave interpreter. Tk provides a quick and fun way to generate user interfaces. In this chapter we will go through a series of short example programs to give you a feel for what you can do. Some details are glossed over in this chapter and considered in more detail later. In particular, the pack geometry manager is covered in Chapter 25 and event bindings are discussed inChapter 29. The Tk widgets are discussed in more detail in later chapters. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] ExecLog Our first example provides a simple user interface to running another program with the exec command. The interface consists of two buttons, Run it and Quit, an entry widget in which to enter a command, and a text widget in which to log the results of running the program. The script runs the program in a pipeline and uses the fileevent command to wait for output. This structure lets the user interface remain responsive while the program executes. You could use this to run make, for example, and it would save the results in the log. The complete example is given first, and then its commands are discussed in more detail. Example 24-1 Logging the output of a program run with exec #!/usr/local/bin/wish # execlog - run a program with exec and log the output # Set window title wm title . ExecLog # Create a frame for buttons and entry. frame .top -borderwidth 10 pack .top -side top -fill x # Create the command buttons. button .top.quit -text Quit -command exit set but [button .top.run -text "Run it" -command Run] pack .top.quit .top.run -side right This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks # Create a labeled entry for the command label .top.l -text Command: -padx 0 entry .top.cmd -width 20 -relief sunken \ -textvariable command pack .top.l -side left pack .top.cmd -side left -fill x -expand true # Set up key binding equivalents to the buttons bind .top.cmd <Return> Run bind .top.cmd <Control-c> Stop focus .top.cmd # Create a text widget to log the output frame .t set log [text .t.log -width 80 -height 10 \ -borderwidth 2 -relief raised -setgrid true \ -yscrollcommand {.t.scroll set}] scrollbar .t.scroll -command {.t.log yview} pack .t.scroll -side right -fill y pack .t.log -side left -fill both -expand true pack .t -side top -fill both -expand true # Run the program and arrange to read its input proc Run {} { global command input log but if [catch {open "|$command |& cat"} input] { $log insert end $input\n } else { fileevent $input readable Log $log insert end $command\n $but config -text Stop -command Stop } } # Read and log output from the program proc Log {} { global input log if [eof $input] { Stop } else { gets $input line $log insert end $line\n $log see end } } # Stop the program and fix up the button proc Stop {} { global input but catch {close $input} $but config -text "Run it" -command Run This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } Window Title The first command sets the title that appears in the title bar implemented by the window manager. Recall that dot (i.e., .) is the name of the main window: wm title . ExecLog The wm command communicates with the window manager. The window manager is the program that lets you open, close, and resize windows. It implements the title bar for the window and probably some small buttons to close or resize the window. Different window managers have a distinctive look; the figure shows a title bar from twm, a window manager for X. A Frame for Buttons A frame is created to hold the widgets that appear along the top of the interface. The frame has a border to provide some space around the widgets: frame .top -borderwidth 10 The frame is positioned in the main window. The default packing side is the top, so -side top is redundant here, but it is used for clarity. The -fill x packing option makes the frame fill out to the whole width of the main window: pack .top -side top -fill x Command Buttons Two buttons are created: one to run the command, the other to quit the program. Their names, .top.quit and .top.run, imply that they are children of the .top frame. This affects the pack command, which positions widgets inside their parent by default: button .top.quit -text Quit -command exit set but [button .top.run -text "Run it" \ -command Run] pack .top.quit .top.run -side right A Label and an Entry This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks The label and entry are also created as children of the .top frame. The label is created with no padding in the X direction so that it can be positioned right next to the entry. The size of the entry is specified in terms of characters. The relief attribute gives the entry some looks to set it apart visually on the display. The contents of the entry widget are linked to the Tcl variable command: label .top.l -text Command: -padx 0 entry .top.cmd -width 20 -relief sunken \ -textvariable command The label and entry are positioned to the left inside the .top frame. The additional packing parameters to the entry allow it to expand its packing space and fill up that extra area with its display. The difference between packing space and display space is discussed in Chapter 25 on page 399: pack .top.l -side left pack .top.cmd -side left -fill x -expand true Key Bindings and Focus Key bindings on the entry widget provide an additional way to invoke the functions of the application. The bind command associates a Tcl command with an event in a particular widget. The <Return> event is generated when the user presses theReturn key on the keyboard. The <Control-c> event is generated when the letter c is typed while the Control key is already held down. For the events to go to the entry widget, .top.cmd, input focus must be given to the widget. By default, an entry widget gets the focus when you click the left mouse button in it. The explicit focus command is helpful for users with the focus-follows-mouse model. As soon as the mouse is over the main window the user can type into the entry: bind .top.cmd <Return> Run bind .top.cmd <Control-c> Stop focus .top.cmd A Resizable Text and Scrollbar A text widget is created and packed into a frame with a scrollbar. The width and height of the text widget are specified in characters and lines, respectively. The setgrid attribute of the text widget is turned on. This restricts the resize so that only a whole number of lines and average-sized characters can be displayed. The scrollbar is a separate widget in Tk, and it can be connected to different widgets using the same setup as is used here. The text's yscrollcommand updates the display of the scrollbar when the text widget is modified, and the scrollbar'scommand scrolls the associated widget when the user manipulates the scrollbar: frame .t set log [text .t.log -width 80 -height 10 \ -borderwidth 2 -relief raised -setgrid true\ This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks -yscrollcommand {.t.scroll set}] scrollbar .t.scroll -command {.t.log yview} pack .t.scroll -side right -fill y pack .t.log -side left -fill both -expand true pack .t -side top -fill both -expand true A side effect of creating a Tk widget is the creation of a new Tcl command that operates on that widget. The name of the Tcl command is the same as the Tk pathname of the widget. In this script, the text widget command, .t.log, is needed in several places. However, it is a good idea to put the Tk pathname of an important widget into a variable because that pathname can change if you reorganize your user interface. The disadvantage of this is that you must declare the variable with global inside procedures. The variable log is used for this purpose in this example to demonstrate this style. The Run Procedure The Run procedure starts the program specified in the command entry. That value is available in the global command variable because of the textvariable attribute of the entry. The command is run in a pipeline so that it executes in the background. The leading | in the argument to open indicates that a pipeline is being created. The catch command guards against bogus commands. The variableinput is set to an error message, or to the normal open return that is a file descriptor. The program is started like this: if [catch {open "|$command |& cat"} input] { Trapping errors from pipelines. The pipeline diverts error output from the command through the cat program. If you do not use cat like this, then the error output from the pipeline, if any, shows up as an error message when the pipeline is closed. In this example it turns out to be awkward to distinguish between errors generated from the program and errors generated because of the way the Stop procedure is implemented. Furthermore, some programs interleave output and error output, and you might want to see the error output in order instead of all at the end. If the pipeline is opened successfully, then a callback is set up using the fileevent command. Whenever the pipeline generates output, then the script can read data from it. The Log procedure is registered to be called whenever the pipeline is readable: fileevent $input readable Log The command (or the error message) is inserted into the log. This is done using the name of the text widget, which is stored in the log variable, as a Tcl command. The value of the command is appended to the log, and a newline is added so that its output will appear on the next line. $log insert end $command\n The text widget's insert function takes two parameters: amark and a string to insert at that mark. The symbolic markend represents the end of the contents of the text widget. The run button is changed into a stop button after the program begins. This avoids a cluttered interface and demonstrates the dynamic nature of a Tk interface. Again, because this button is used in a few different places in the script, its pathname has been stored in the variable but: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks $but config -text Stop -command Stop The Log Procedure The Log procedure is invoked whenever data can be read from the pipeline, and when end of file has been reached. This condition is checked first, and the Stop procedure is called to clean things up. Otherwise, one line of data is read and inserted into the log. The text widget's see operation is used to position the view on the text so that the new line is visible to the user: if [eof $input] { Stop } else { gets $input line $log insert end $line\n $log see end } The Stop Procedure The Stop procedure terminates the program by closing the pipeline. Theclose is wrapped up with a catch. This suppresses the errors that can occur when the pipeline is closed prematurely on the process. Finally, the button is restored to its run state so that the user can run another command: catch {close $input} $but config -text "Run it" -command Run In most cases, closing the pipeline is adequate to kill the job. On UNIX, this results in a signal, SIGPIPE, being delivered to the program the next time it does a write to its standard output. There is no built-in way to kill a process, but you can exec the UNIX kill program. The pid command returns the process IDs from the pipeline: foreach pid [pid $input] { catch {exec kill $pid} } If you need more sophisticated control over another process, you should check out the expect Tcl extension, which is described in the book Exploring Expect (Don Libes, O'Reilly & Associates, Inc., 1995).Expect provides powerful control over interactive programs. You can write Tcl scripts that send input to interactive programs and pattern match on their output. Expect is designed to automate the use of programs that were designed for interactive use. Cross-Platform Issues This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks This script will run on UNIX and Windows, but not on Macintosh because there is no exec command. One other problem is the binding for <Control-c> to cancel the job. This is UNIX-like, while Windows users expect<Escape> to cancel a job, and Macintosh users expect <Command-period>. Platform_CancelEvent defines a virtual event, <<Cancel>>, and Stop is bound to it: Example 24-2 A platform-specific cancel event proc Platform_CancelEvent {} { global tcl_platform switch $tcl_platform(platform) { unix { event add <<Cancel>> <Control-c> } windows { event add <<Cancel>> <Escape> } macintosh { event add <<Cancel>> <Command-period> } } } bind .top.entry <<Cancel>> Stop There are other virtual events already defined by Tk. Theevent command and virtual events are described on page 446. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] The Example Browser Example 24-3 is a browser for the code examples that appear in this book. The basic idea is to provide a menu that selects the examples, and a text window to display the examples. Before you can use this sample program, you need to edit it to set the proper location of the exsource directory that contains all the example sources from the book. Example 24-4 on page 389 extends the browser with a shell that is used to test the examples. Example 24-3 A browser for the code examples in the book #!/usr/local/bin/wish # Browser for the Tcl and Tk examples in the book. # browse(dir) is the directory containing all the tcl files # Please edit to match your system configuration. switch $tcl_platform(platform) { "unix" {set browse(dir) /cdrom/tclbook2/exsource} "windows" {set browse(dir) D:/exsource} "macintosh" {set browse(dir) /tclbook2/exsource} } wm minsize . 30 5 wm title . "Tcl Example Browser" # Create a row of buttons along the top set f [frame .menubar] pack $f -fill x button $f.quit -text Quit -command exit button $f.next -text Next -command Next button $f.prev -text Previous -command Previous # The Run and Reset buttons use EvalEcho that # is defined by the Tcl shell in Example 24–4 on page 389 button $f.load -text Run -command Run button $f.reset -text Reset -command Reset pack $f.quit $f.reset $f.load $f.next $f.prev -side right # A label identifies the current example label $f.label -textvariable browse(current) pack $f.label -side right -fill x -expand true This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks # Create the menubutton and menu menubutton $f.ex -text Examples -menu $f.ex.m pack $f.ex -side left set m [menu $f.ex.m] # Create the text to display the example # Scrolled_Text is defined in Example 33–1 on page 500 set browse(text) [Scrolled_Text .body \ -width 80 -height 10\ -setgrid true] pack .body -fill both -expand true # Look through the example files for their ID number. foreach f [lsort -dictionary [glob [file join $browse(dir) *]]] { if [catch {open $f} in] { puts stderr "Cannot open $f: $in" continue } while {[gets $in line] >= 0} { if [regexp {^# Example ([0-9]+)-([0-9]+)} $line \ x chap ex] { lappend examples($chap) $ex lappend browse(list) $f # Read example title gets $in line set title($chap-$ex) [string trim $line "# "] set file($chap-$ex) $f close $in break } } } # Create two levels of cascaded menus. # The first level divides up the chapters into chunks. # The second level has an entry for each example. option add *Menu.tearOff 0 set limit 8 set c 0; set i 0 foreach chap [lsort -integer [array names examples]] { if {$i == 0} { $m add cascade -label "Chapter $chap..." \ -menu $m.$c set sub1 [menu $m.$c] incr c } set i [expr ($i +1) % $limit] $sub1 add cascade -label "Chapter $chap" -menu $sub1.sub$i set sub2 [menu $sub1.sub$i] foreach ex [lsort -integer $examples($chap)] { $sub2 add command -label "$chap-$ex $title($chap-$ex)" \ -command [list Browse $file($chap-$ex)] } } This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Display a specified file. The label is updated to # reflect what is displayed, and the text is left # in a read-only mode after the example is inserted. proc Browse { file } { global browse set browse(current) [file tail $file] set browse(curix) [lsearch $browse(list) $file] set t $browse(text) $t config -state normal $t delete 1.0 end if [catch {open $file} in] { $t insert end $in } else { $t insert end [read $in] close $in } $t config -state disabled } # Browse the next and previous files in the list set browse(curix) -1 proc Next {} { global browse if {$browse(curix) < [llength $browse(list)] - 1} { incr browse(curix) } Browse [lindex $browse(list) $browse(curix)] } proc Previous {} { global browse if {$browse(curix) > 0} { incr browse(curix) -1 } Browse [lindex $browse(list) $browse(curix)] } # Run the example in the shell proc Run {} { global browse EvalEcho [list source \ [file join $browse(dir) $browse(current)]] } # Reset the slave in the eval server proc Reset {} { EvalEcho reset } More about Resizing Windows This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks This example uses the wm minsize command to put a constraint on the minimum size of the window. The arguments specify the minimum width and height. These values can be interpreted in two ways. By default they are pixel values. However, if an internal widget has enabled geometry gridding, then the dimensions are in grid units of that widget. In this case the text widget enables gridding with its setgrid attribute, so the minimum size of the window is set so that the text window is at least 30 characters wide by five lines high: wm minsize . 30 5 In older versions of Tk, Tk 3.6, gridding also enabled interactive resizing of the window. Interactive resizing is enabled by default in Tk 4.0 and later. Managing Global State The example uses the browse array to collect its global variables. This makes it simpler to reference the state from inside procedures because only the array needs to be declared global. As the application grows over time and new features are added, that global command won't have to be adjusted. This style also serves to emphasize what variables are important. The browse array holds the name of the example directory (dir), the Tk pathname of the text display (text), and the name of the current file (current). The list and curix elements are used to implement the Next and Previous procedures. Searching through Files The browser searches the file system to determine what it can display. The tcl_platform(platform) variable is used to select a different example directory on different platforms. You may need to edit the on-line example to match your system. The example uses glob to find all the files in the exsource directory. The file join command is used to create the file name pattern in a platform-independent way. The result of glob is sorted explicitly so the menu entries are in the right order. Each file is read one line at a time with gets, and then regexp is used to scan for keywords. The loop is repeated here for reference: foreach f [lsort -dictionary [glob -directory $browse(dir) *]] { if {[catch {open $f} in]} { puts stderr "Cannot open $f: $in" continue } while {[gets $in line] >= 0} { if {[regexp {^# Example ([0-9]+)-([0-9]+)} $line \ x chap ex]} { lappend examples($chap) $ex lappend browse(list) $f # Read example title gets $in line set title($chap-$ex) [string trim $line "# "] set file($chap-$ex) $f close $in This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks break } } } The example files contain lines like this: # Example 1-1 # The Hello, World! program The regexp picks out the example numbers with the([0-9]+)-([0-9]+) part of the pattern, and these are assigned to thechap and ex variables. The x variable is assigned the value of the whole match, which is more than we are interested in. Once the example number is found, the next line is read to get the description of the example. At the end of the foreach loop the examples array has an element defined for each chapter, and the value of each element is a list of the examples for that chapter. Cascaded Menus The values in the examples array are used to build up a cascaded menu structure. First a menubutton is created that will post the main menu. It is associated with the main menu with its menu attribute. The menu must be a child of the menubutton for its display to work properly: menubutton $f.ex -text Examples -menu $f.ex.m set m [menu $f.ex.m] There are too many chapters to put them all into one menu. The main menu has a cascade entry for each group of eight chapters. Each of these submenus has a cascade entry for each chapter in the group, and each chapter has a menu of all its examples. Once again, the submenus are defined as a child of their parent menu. Note the inconsistency between menu entries and buttons. Their text is defined with the -label option, not -text. Other than this they are much like buttons.Chapter 30 describes menus in more detail. The code is repeated here: set limit 8 ; set c 0 ; set i 0 foreach key [lsort -integer [array names examples]] { if {$i == 0} { $m add cascade -label "Chapter $key..." \ -menu $m.$c set sub1 [menu $m.$c] incr c } set i [expr {($i +1) % $limit}] $sub1 add cascade -label "Chapter $key" -menu $sub1.sub$i set sub2 [menu $sub1.sub$i] foreach ex [lsort -integer $examples($key)] { $sub2 add command -label "$key-$ex $title($key-$ex)" \ -command [list Browse $file($key-$ex)] } } A Read-Only Text Widget This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks The Browse procedure is fairly simple. It sets browse(current) to be the name of the file. This changes the main label because of its textvariable attribute that links it to this variable. Thestate attribute of the text widget is manipulated so that the text is read-only after the text is inserted. You have to set the state to normal before inserting the text; otherwise, theinsert has no effect. Here are a few commands from the body of Browse: global browse set browse(current) [file tail $file] $t config -state normal $t insert end [read $in] $t config -state disabled [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] A Tcl Shell This section demonstrates the text widget with a simple Tcl shell application. It uses a text widget to prompt for commands and display their results. It uses a second Tcl interpreter to evaluate the commands you type. This dual interpreter structure is used by the console built into the Windows and Macintosh versions of wish. The TkCon application written by Jeff Hobbs is an even more elaborate console that has many features to support interactive Tcl use: http://tkcon.sourceforge.net/ Example 24-4 is written to be used with the browser fromExample 24-3 in the same application. The browser'sRun button runs the current example in the shell. An alternative is to have the shell run as a separate process and use the send command to communicate Tcl commands between separate applications. That alternative is shown in Example 43-2 on page 651. Example 24-4 A Tcl shell in a text widget #!/usr/local/bin/wish # Simple evaluator. It executes Tcl in a slave interpreter set t [Scrolled_Text .eval -width 80 -height 10] pack .eval -fill both -expand true # Text tags give script output, command errors, command # results, and the prompt a different appearance $t tag configure prompt -underline true $t tag configure result -foreground purple $t tag configure error -foreground red $t tag configure output -foreground blue # Insert the prompt and initialize the limit mark set eval(prompt) "tcl> " $t insert insert $eval(prompt) prompt $t mark set limit insert $t mark gravity limit left focus $t set eval(text) $t # Key bindings that limit input and eval things. The break in # the bindings skips the default Text binding for the event. bind $t <Return> {EvalTypein ; break} bind $t <BackSpace> { if {[%W tag nextrange sel 1.0 end] != ""} { This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks %W delete sel.first sel.last } elseif {[%W compare insert > limit]} { %W delete insert-1c %W see insert } break } bind $t <Key> { if [%W compare insert < limit] { %W mark set insert end } } # Evaluate everything between limit and end as a Tcl command proc EvalTypein {} { global eval $eval(text) insert insert \n set command [$eval(text) get limit end] if [info complete $command] { $eval(text) mark set limit insert Eval $command } } # Echo the command and evaluate it proc EvalEcho {command} { global eval $eval(text) mark set insert end $eval(text) insert insert $command\n Eval $command } # Evaluate a command and display its result proc Eval {command} { global eval $eval(text) mark set insert end if [catch {$eval(slave) eval $command} result] { $eval(text) insert insert $result error } else { $eval(text) insert insert $result result } if {[$eval(text) compare insert != "insert linestart"]} { $eval(text) insert insert \n } $eval(text) insert insert $eval(prompt) prompt $eval(text) see insert $eval(text) mark set limit insert return } # Create and initialize the slave interpreter proc SlaveInit {slave} { interp create $slave load {} Tk $slave interp alias $slave reset {} ResetAlias $slave This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks interp alias $slave puts {} PutsAlias $slave return $slave } # The reset alias deletes the slave and starts a new one proc ResetAlias {slave} { interp delete $slave SlaveInit $slave } # The puts alias puts stdout and stderr into the text widget proc PutsAlias {slave args} { if {[llength $args] > 3} { error "invalid arguments" } set newline "\n" if {[string match "-nonewline" [lindex $args 0]]} { set newline "" set args [lreplace $args 0 0] } if {[llength $args] == 1} { set chan stdout set string [lindex $args 0]$newline } else { set chan [lindex $args 0] set string [lindex $args 1]$newline } if [regexp (stdout|stderr) $chan] { global eval $eval(text) mark gravity limit right $eval(text) insert limit $string output $eval(text) see limit $eval(text) mark gravity limit left } else { puts -nonewline $chan $string } } set eval(slave) [SlaveInit shell] Text Marks, Tags, and Bindings The shell uses a text mark and some extra bindings to ensure that users only type new text into the end of the text widget. A mark represents a position in the text that is updated as characters are inserted and deleted. The limit mark keeps track of the boundary between the read-only area and the editable area. The insert mark is where the cursor is displayed. The end mark is always the end of the text. TheEvalTypein procedure looks at all the text between limit and end to see if it is a complete Tcl command. If it is, it evaluates the command in the slave interpreter. The <Key> binding checks to see where the insert mark is and bounces it to the end if the user tries to input text before thelimit mark. The puts This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks alias sets right gravity on limit, so the mark is pushed along when program output is inserted right atlimit. Otherwise, the left gravity on limit means that the mark does not move when the user inserts right at limit. Text tags are used to give different regions of text difference appearances. A tag applies to a range of text. The tags are configured at the beginning of the script and they are applied when text is inserted. Chapter 36 describes the text widget in more detail. Multiple Interpreters The SlaveInit procedure creates another interpreter to evaluate the commands. This prevents conflicts with the procedures and variables used to implement the shell. Initially, the slave interpreter only has access to Tcl commands. The load command installs the Tk commands, and it creates a new top-level window that is "." for the slave interpreter. Chapter 20 describes how you can embed the window of the slave within other frames. The shell interpreter is not created with the -safe flag, so it can do anything. For example, if you typeexit, it will exit the whole application. The SlaveInit procedure installs an alias, reset, that just deletes the slave interpreter and creates a new one. You can use this to clean up after working in the shell for a while. Chapter 19 describes the interp command in detail. Native Look and Feel When you run a Tk script on different platforms, it uses native buttons, menus, and scrollbars. The text and entry widgets are tuned to give the application the native look and feel. The following screen shots show the combined browser and shell as it looks on Macintosh, Windows, and UNIX. Example 24-5. Macintosh look and feel. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Example 24-6. Windows look and feel. Example 24-7. UNIX look and feel. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 25. The Pack Geometry Manager This chapter explores the pack geometry manager that positions widgets on the screen. Geometry managers arrange widgets on the screen. This chapter describes the pack geometry manager, which is a constraint-based system. The next two chapters describe the grid and place geometry managers. The pack and grid geometry managers are quite general, whileplace is used for special-purpose applications. This book uses pack a lot because it was the original geometry manager for Tk. Thegrid geometry manager was added in Tk 4.1. A geometry manager uses one widget as a parent, and it arranges multiplechildren (also called slaves) inside the parent. The parent is almost always a frame, but this is not strictly necessary. A widget can only be managed by one geometry manager at a time, but you can use different managers to control different widgets in your user interface. If a widget is not managed, then it doesn't appear on your display at all. Don't pack and grid into the same manager widget. For each individual manager widget — such as a frame, a labelframe, or a toplevel — you have the choice of using either pack or grid to manage all of its immediate children. Attempting to use both in the same manager results in an endless loop as both geometry managers try to control the window layout. This restriction applies only to the immediate children of a manager widget; you can use a different geometry manager for "descendents" that aren't immediate children. For example, you can choose to pack all of the immediate children of the . toplevel. Then, if one of the children of . is a frame, you can choose to use either pack or grid to manage the children of that frame. The packer is a powerful constraint-based geometry manager. Instead of specifying in detail the placement of each window, the programmer defines some constraints about how windows should be positioned, and the packer works out the details. It is important to understand the algorithm the packer uses; otherwise, the constraint-based results may not be what you expect. This chapter explores the packer through a series of examples. The background of the main window is set to black, and the other frames are given different colors so you can identify frames and observe the effect of the different packing parameters. When consecutive examples differ by a small amount, the added command or option is printed in bold courier to highlight the addition. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Packing toward a Side The following example creates two frames and packs them toward the top side of the main window. The upper frame, .one, is not as big and the main window shows through on either side. The children are packed toward the specified side in order, so .one is on top. The four possible sides are: top, right, bottom, and left. The top side is the default. Example 25-1 Two frames packed inside the main frame # Make the main window black . config -bg black # Create and pack two frames frame .one -width 40 -height 40 -bg white frame .two -width 100 -height 50 -bg grey50 pack .one .two -side top Shrinking Frames and pack propagate In the previous example, the main window shrank down to be just large enough to hold its two children. In most cases this is the desired behavior. If not, you can turn it off with the pack propagate command. Apply this to the parent frame, and it will not adjust its size to fit its children: Example 25-2 Turning off geometry propagation This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than frame .one -width 40 -height 40 -bg white frame .two -width 100 -height 50 -bg grey50 pack propagate . false pack .one .two -side top [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Horizontal and Vertical Stacking In general, you use either horizontal or vertical stacking within a frame. If you mix sides such as left and top, the effect might not be what you expect. Instead, you should introduce more frames to pack a set of widgets into a stack of a different orientation. For example, suppose we want to put a row of buttons inside the upper frame in the examples we have given so far: Example 25-3 A horizontal stack inside a vertical stack frame .one -bg white frame .two -width 100 -height 50 -bg grey50 # Create a row of buttons foreach b {alpha beta gamma} { button .one.$b -text $b pack .one.$b -side left } pack .one .two -side top Example 25-4 Even more nesting of horizontal and vertical stacks frame .one -bg white frame .two -width 100 -height 50 -bg grey50 foreach b {alpha beta} { This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks button .one.$b -text $b pack .one.$b -side left } # Create a frame for two more buttons frame .one.right foreach b {delta epsilon} { button .one.right.$b -text $b pack .one.right.$b -side bottom } pack .one.right -side right pack .one .two -side top You can build more complex arrangements by introducing nested frames and switching between horizontal and vertical stacking as you go. Within each frame pack all the children with either a combination of -side left and -side right, or -side top and -side bottom. Example 25-4 replaces the .one.gamma button with a vertical stack of two buttons,.one.right.delta and .one.right.epsilon. These are packed toward the bottom of .one.right, so the first one packed is on the bottom. The frame .one.right was packed to the right, and in the previous example, the button.one.gamma was packed to the left. Despite the difference, they ended up in the same position relative to the other two widgets packed inside the .one frame. The next section explains why. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] The Cavity Model The packing algorithm is based on a cavity model for the available space inside a frame. For example, when the main wish window is created, the main frame is empty and there is an obvious space, or cavity, in which to place widgets. The primary rule about the packing cavity is a widget occupies one whole side of the cavity. To demonstrate this, pack three widgets into the main frame. Put the first two on the bottom, and the third one on the right: Example 25-5 Mixing bottom and right packing sides # pack two frames on the bottom. frame .one -width 100 -height 50 -bg grey50 frame .two -width 40 -height 40 -bg white pack .one .two -side bottom # pack another frame to the right frame .three -width 20 -height 20 -bg grey75 pack .three -side right When we pack a third frame into the main window with -side left or -side right, the new frame is positioned inside the cavity, which is above the two frames already packed toward the bottom side. The frame does not appear to the right of the existing frames as you might have expected. This is because the .two frame occupies the whole bottom side of the packing cavity, even though its display does not fill up that side. Can you tell where the packing cavity is after this example? It is to the left of the frame .three, which is the last frame packed toward the right, and it is above the frame .two, which is the last frame packed toward the bottom. This explains why there was no difference between the previous two examples when .one.gamma was packed to the left, but .one.right was packed to the right. At that point, packing to the left or right of the cavity had the same effect. However, it will affect what happens if another widget is packed into those two configurations. Try out [*] the following commands after running Example 25-3 and Example 25-4 and compare the difference. [*] Answer: After Example 25-3 the new button is to the right of all buttons. AfterExample 25-4 the new button is This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks between .one.beta and .one.right. button .one.omega -text omega pack .one.omega -side right Each packing parent has its own cavity, which is why introducing nested frames can help. If you use a horizontal or vertical arrangement inside any given frame, you can more easily simulate the packer's behavior in your head! [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Packing Space and Display Space The packer distinguishes between packing space and display space when it arranges the widgets. The display space is the area requested by a widget for the purposes of painting itself. The packing space is the area the packer allows for the placement of the widget. Because of geometry constraints, a widget may be allocated more (or less) packing space than it needs to display itself. The extra space, if any, is along the side of the cavity against which the widget was packed. The -fill Option The -fill packing option causes a widget to fill up the allocated packing space with its display. A widget can fill in the X or Y direction, or both. The default is not to fill, which is why the black background of the main window has shown through in the examples so far: Example 25-6 Filling the display into extra packing space frame .one -width 100 -height 50 -bg grey50 frame .two -width 40 -height 40 -bg white # Pack with fill enabled pack .one .two -side bottom -fill x frame .three -width 20 -height 20 -bg red pack .three -side right -fill x This is just like Example 25-5, except that -fill x has been specified for all the frames. The.two frame fills, but the .three frame does not. This is because the fill does not expand into the packing cavity. In fact, after this example, the packing cavity is the part that shows through in black. Another way to look at this is that the .two frame was allocated the whole bottom side of the packing cavity, so its fill can expand the frame to This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks occupy that space. The .three frame has only been allocated the right side, so a fill in the X direction will not have any effect. Another use of fill is for a menu bar that has buttons at either end and some empty space between them. The frame that holds the buttons is packed toward the top. The buttons are packed into the left and right sides of the menu bar frame. Without fill, the menu bar shrinks to be just large enough to hold all the buttons, and the buttons are squeezed together. When fill is enabled in the X direction, the menu bar fills out the top edge of the display: Example 25-7 Using horizontal fill in a menu bar frame .menubar -bg white frame .body -width 150 -height 50 -bg grey50 # Create buttons at either end of the menubar foreach b {alpha beta} { button .menubar.$b -text $b } pack .menubar.alpha -side left pack .menubar.beta -side right # Let the menu bar fill along the top pack .menubar -side top -fill x pack .body Internal Padding with -ipadx and -ipady Another way to get more fill space is with the -ipadx and -ipady packing options that request more display space in the X and Y directions, respectively. Due to other constraints the request might not be offered, but in general you can use this to give a widget more display space. The next example is just like the previous one except that some internal padding has been added: Example 25-8 The effects of internal padding (-ipady) This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Create and pack two frames frame .menubar -bg white frame .body -width 150 -height 50 -bg grey50 # Create buttons at either end of the menubar foreach b {alpha beta} { button .menubar.$b -text $b } pack .menubar.alpha -side left -ipady 10 pack .menubar.beta -side right -ipadx 10 # Let the menu bar fill along the top pack .menubar -side top -fill x -ipady 5 pack .body The alpha button is taller and thebeta button is wider because of the internal padding. The frame has internal padding, which reduces the space available for the packing cavity, so the .menubar frame shows through above and below the buttons. Some widgets have attributes that result in more display space. For example, it would be hard to distinguish a frame with width 50 and no internal padding from a frame with width 40 and a -ipadx 5 packing option. The packer would give the frame5 more pixels of display space on either side for a total width of 50. Buttons have their own -padx and -pady options that give them more display space, too. This padding provided by the button is used to keep its text away from the edge of the button. The following example illustrates the difference. The -anchor e button option positions the text as far to the right as possible. Example 40-5 on page 617 provides another comparison of these options: Example 25-9 Button padding vs. packer padding # Foo has internal padding from the packer button .foo -text Foo -anchor e -padx 0 -pady 0 pack .foo -side right -ipadx 10 -ipady 10 # Bar has its own padding button .bar -text Bar -anchor e -pady 10 -padx 10 pack .bar -side right -ipadx 0 -ipady 0 In all cases, you can specify the amount of padding using any type of screen distance recognized by Tk. A simple numeric value is interpreted as pixels. You can also follow a number with one of i, m, c, or p, which is interpreted as inches, millimeters, centimeters, or typographic points, respectively. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks External Padding with -padx and -pady The packer can provide external padding that allocates packing space that cannot be filled. The space is outside of the border that widgets use to implement their 3D reliefs. Example 40-2 on page 614 shows the different reliefs. The look of a default button is achieved with an extra frame and some padding: Example 25-10 The look of a default button . config -borderwidth 10 # OK is the default button frame .ok -borderwidth 2 -relief sunken button .ok.b -text OK pack .ok.b -padx 5 -pady 5 # Cancel is not button .cancel -text Cancel pack .ok .cancel -side left -padx 5 -pady 5 The .ok.b button looks the same even if it is packed with-fill both. The child widgets do not fill the external padding provided by the packer. Example 25-10 handcrafts the look of a default button. Tk 8.0 added a-default attribute for buttons that gives them the right appearance for the default button on the current platform. It looks somewhat like this on UNIX, but the appearance is different on Macintosh and Windows. Tk 8.4 added the ability to specify asymmetric padding as a list of two screen distances. For example, the following adds 5 pixels of padding to the left and right of the widgets, 3 pixels above them, and 6 pixels below them: pack .ok .cancel -side left -padx 5 -pady {3 6} [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Resizing and -expand The -expand true packing option lets a widget expand its packing space into unclaimed space in the packing cavity. Example 25-6 could use this on the small frame on top to get it to expand across the top of the display, even though it is packed to the right side. The more common case occurs when you have a resizable window. When the user makes the window larger, the widgets have to be told to take advantage of the extra space. Suppose you have a main widget like a text, listbox, or canvas that is in a frame with a scrollbar. That frame has to be told to expand into the extra space in its parent (e.g., the main window) and then the main widget (e.g., the canvas) has to be told to expand into its parent frame. Example 24-1 on page 378 does this. In nearly all cases the -fill both option is used along with-expand true so that the widget actually uses its extra packing space for its own display. The converse is not true. There are many cases where a widget should fill extra space but not attempt to expand into the packing cavity. The examples below show the difference. Now we can investigate what happens when the window is made larger. The next example starts like Example 25-7 on page 400, but the size of the main window is increased: Example 25-11 Resizing without the expand option # Make the main window black . config -bg black # Create and pack two frames frame .menubar -bg white frame .body -width 150 -height 50 -bg grey50 # Create buttons at either end of the menubar foreach b {alpha beta} { button .menubar.$b -text $b } pack .menubar.alpha -side left pack .menubar.beta -side right # Let the menu bar fill along the top pack .menubar -side top -fill x pack .body # Resize the main window to be bigger wm geometry . 200x100 . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Allow interactive resizing wm minsize . 100 50 The only widget that claims any of the new space is .menubar because of its -fill x packing option. The .body frame needs to be packed properly: Example 25-12 Resizing with expand turned on # Use all of Example 25–11 then repack .body pack .body -expand true -fill both If more than one widget inside the same parent is allowed to expand, then the packer shares the extra space between them proportionally. This is probably not the effect you want in the examples we have built so far. The .menubar, for example, is not a good candidate for expansion. Example 25-13 More than one expanding widget # Use all of Example 25–11 then repack .menubar and .body pack .menubar -expand true -fill x pack .body -expand true -fill both [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Anchoring If a widget is left with more packing space than display space, you can position it within its packing space using the -anchor packing option. The default anchor position is center. The other options correspond to points on a compass:n, ne, e, se, s, sw, w, and nw: Example 25-14 Setup for anchor experiments # Make the main window black . config -bg black # Create two frames to hold open the cavity frame .prop -bg white -height 80 -width 20 frame .base -width 120 -height 20 -bg grey50 pack .base -side bottom # Float a label and the prop in the cavity label .foo -text Foo pack .prop .foo -side right -expand true The .base frame is packed on the bottom. Then the.prop frame and the .foo label are packed to the right with expand set but no fill. Instead of being pressed up against the right side, the expand gives each of these widgets half of the extra space in the X direction. Their default anchor of center results in the positions shown. The next example shows some different anchor positions: Example 25-15 The effects of noncenter anchors . config -bg black . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Create two frames to hold open the cavity frame .prop -bg white -height 80 -width 20 frame .base -width 120 -height 20 -bg grey50 pack .base -side bottom # Float the label and prop # Change their position with anchors label .foo -text Foo pack .prop -side right -expand true -anchor sw pack .foo -side right -expand true -anchor ne The label has room on all sides, so each of the different anchors will position it differently. The.prop frame only has room in the X direction, so it can only be moved into three different positions: left, center, and right. Any of the anchors w, nw, and sw result in the left position. The anchors center, n, and s result in the center position. The anchorse, se, and ne result in the right position. If you want to see all the variations, type in the following commands to animate the different packing anchors. The update idletasks forces any pending display operations. The after 500 causes the script to wait for500 milliseconds: Example 25-16 Animating the packing anchors foreach anchor {center n ne e se s sw w nw center} { pack .foo .prop -anchor $anchor # Update the display update idletasks # Wait half a second after 500 } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Packing Order The packer maintains an order among the children that are packed into a frame. By default, each new child is appended to the end of the packing order. The most obvious effect of the order is that the children first in the packing order are closest to the side they are packed against. You can control the packing order with the -before and -after packing options, and you can reorganize widgets after they have already been packed: Example 25-17 Controlling the packing order # Create five labels in order foreach label {one two three four five} { label .$label -text $label pack .$label -side left -padx 5 } # ShuffleUp moves a widget to the beginning of the order proc ShuffleUp { parent child } { set first [lindex [pack slaves $parent] 0] pack $child -in $parent -before $first } # ShuffleDown moves a widget to the end of the order proc ShuffleDown { parent child } { pack $child -in $parent } ShuffleUp . .five ShuffleDown . .three Introspection The pack slaves command returns the list of children in their packing order. TheShuffleUp procedure uses this to find out the first child so that it can insert another child before it. The ShuffleDown procedure is simpler because the default is to append the child to the end of the packing order. When a widget is repacked, then it retains all its packing parameters that have already been set. If you need to examine the current packing parameters for a widget, use the pack info command. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks pack info .five => -in . -anchor center -expand 0 -fill none -ipadx 0 \ -ipady 0 -padx 0 -pady 0 -side left Pack the Scrollbar First The packing order also determines what happens when the window is made too small. If the window is made small enough the packer will clip children that come later in the packing order. This is why, when you pack a scrollbar and a text widget into a frame, you should pack the scrollbar first. Otherwise, when the window is made smaller the text widget takes up all the space and the scrollbar is clipped. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Choosing the Parent for Packing In nearly all of the examples in this chapter, a widget is packed into its parent frame. In general, it is possible to pack a widget into any descendent of its parent. For example, the .a.b widget could be packed into .a, .a.c or .a.d.e.f. The -in packing option lets you specify an alternate packing parent. One motivation for this is that the frames introduced to get the arrangement right can cause cluttered names for important widgets. In Example 25-4 on page 398, the buttons have names like.one.alpha and .one.right.delta, which is not consistent. Here is an alternate implementation of the same example that simplifies the button names and gives the same result: Example 25-18 Packing into other relatives # Create and pack two frames frame .one -bg white frame .two -width 100 -height 50 -bg grey50 # Create a row of buttons foreach b {alpha beta} { button .$b -text $b pack .$b -in .one -side left } # Create a frame for two more buttons frame .one.right foreach b {delta epsilon} { button .$b -text $b pack .$b -in .one.right -side bottom } pack .one.right -side right pack .one .two -side top When you do this, remember that the order in which you create widgets is important. Create the frames first, then create the widgets. The stacking order for windows will cause the later windows to obscure the windows created first. The following is a common mistake because the frame obscures the button: button .a -text hello frame .b pack .a -in .b If you cannot avoid this problem scenario, then you can use theraise command to fix things up. Stacking order is also discussed on page 409. raise .a [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Unpacking a Widget The pack forget command removes a widget from the packing order. The widget gets unmapped, so it is not visible. If you unpack a parent frame, the packing structure inside it is maintained, but all the widgets inside the frame get unmapped. Unpacking a widget is useful if you want to suppress extra features of your interface. You can create all the parts of the interface, and just delay packing them in until the user requests to see them. Then you can pack and unpack them dynamically. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Packer Summary Keep these rules in mind about the packer: Pack vertically (-side top and -side bottom) or horizontally (-side left and -side right) within a frame. Only rarely will a different mixture of packing directions work out the way you want. Add frames to build more complex structures. By default, the packer puts widgets into their parent frame, and the parent frame must be created before the children that are packed into it. If you put widgets into other relatives, remember to create the frames first so the frames stay underneath the widgets packed into them. By default, the packer ignores -width and -height attributes of frames that have widgets packed inside them. It shrinks frames to be just big enough to allow for its border width and to hold the widgets inside them. Use pack propagate to turn off the shrink-wrap behavior. The packer distinguishes between packing space and display space. A widget's display might not take up all the packing space allocated to it. The -fill option causes the display to fill up the packing space in the X or Y directions, or both. The -expand true option causes the packing space to expand into any room in the packing cavity that is otherwise unclaimed. If more than one widget in the same frame wants to expand, then they share the extra space. The -ipadx and -ipady options allocate more display space inside the border, if possible. The -padx and -pady options allocate more packing space outside the border, if possible. The widget never fills this space. These values may be specified as a list of two values to get asymmetric padding (Tk 8.4.) The pack Command Table 25-1 summarizes the pack command. Table 25-2 summarizes the packing options for a widget. These are set with thepack configure command, and the current settings are returned by the pack info command. Table 25-1. The pack command pack win ?win ..? ?options? This is just like pack configure. pack configure win ?win ...? ?options? Packs one or more widgets according to the options, which are given inTable 25-2. pack forget win ?win...? Unpacks the specified windows. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks pack info win Returns the packing parameters of win. pack propagate win ?bool? Queries or sets the geometry propagation of win, which has other widgets packed inside it. pack slaves win Returns the list of widgets managed bywin. Table 25-2. Packing options -after win Packs after win in the packing order. -anchor anchor Anchors: center, n, ne, e, se, s, sw, w, or nw. -before win Packs before win in the packing order. -expand boolean Controls expansion into the unclaimed packing cavity. -fill style Controls fill of packing space. Style:x, y, both, or none. -in win Packs inside win. -ipadx amount Horizontal internal padding, in screen units. -ipady amount Vertical internal padding, in screen units. -padx amount Horizontal external padding, in screen units. May be a list of two screen units for asymmetric padding (Tk 8.4). -pady amount Vertical external padding, in screen units. May be a list of two screen units for asymmetric padding (Tk 8.4). -side side Sides: top, right, bottom, or left. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Window Stacking Order The raise and lower commands control the window stacking order. The stacking order controls the display of windows. Windows higher in the stacking order obscure windows lower in the stacking order. By default, new windows are created at the top of the stacking order so they obscure older windows. Consider this sequence of commands: button .one frame .two pack .one -in .two If you do this, you do not see the button. The problem is that the frame is higher in the stacking order so it obscures the button. You can change the stacking order with theraise command: raise .one .two This puts .one just above .two in the stacking order. If.two was not specified, then .one would be put at the top of the stacking order. The lower command has a similar form. With one argument, it puts that window at the bottom of the stacking order. Otherwise, it puts it just below another window in the stacking order. You can use raise and lower on top-level windows to control their stacking order among all other top-level windows. For example, if a user requests a dialog that is already displayed, use raise to make it pop to the foreground of their cluttered desktop. To determine the stacking order of toplevel windows, use the wm stackorder command. (See "Toplevel Size, Placement, and Decoration" on page 658.) [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 26. The Grid Geometry Manager This chapter explores the grid geometry manager that positions widgets on a grid that automatically adjusts its size. Grid was added in Tk 4.1. The grid geometry manager arranges widgets on a grid with variable-sized rows and columns. You specify the rows and columns occupied by each widget, and the grid is adjusted to accommodate all the widgets it contains. This is ideal for creating table-like layouts. The manager also has sophisticated facilities for controlling row and column sizes and the dynamic resize behavior. By introducing subframes with grids of their own, you can create arbitrary layouts. Don't pack and grid into the same manager widget. As discussed on page 395, you can use a combination of pack and grid to create your display. But for each individual manager widget, you must use only one of pack or grid to manage all of its immediate children. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks A Basic Grid Example 26-1 uses grid to lay out a set of labels and frames in two parallel columns. It takes advantage of the relative placement feature of grid. Instead of specifying rows and columns, the order ofgrid commands and their arguments implies the layout. Eachgrid command starts a new row, and the order of the widgets in the grid command determines the column. In the example, there are two columns, and each iteration of the loop adds a new row. grid makes each column just wide enough to hold the biggest widget. Widgets that are smaller are centered in their cell. That's why the labels appear centered in their column: Example 26-1 A basic grid foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 -height 2 grid .l$color .f$color } The -sticky Setting If a grid cell is larger than the widget inside it, you can control the size and position of the widget with the -sticky option. The -sticky option combines the functions of -fill and -anchor used with the pack geometry manager. You specify to which sides of its cell a widget sticks. You can specify any combination of n, e, w, and s to stick a widget to the top, right, left, and bottom sides of its cell. You can concatenate these letters together (e.g., news) or uses spaces or commas to separate them (e.g.,n,e,w,s). Example 26-2 uses -sticky w to left justify the labels, and -sticky ns to stretch the color frames to the full height of their row: Example 26-2 A grid with sticky settings . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 -height 2 grid .l$color .f$color grid .l$color -sticky w grid .f$color -sticky ns } Example 26-2 uses grid in two ways. The firstgrid in the loop fixes the positions of the widgets because it is the first time they are assigned to the master. The next grid commands modify the existing parameters; they just adjust the-sticky setting because their row and column positions are already known. You can specify row and column positions explicitly with the -row and -column attributes. This is generally more work than using the relative placement, but it is necessary if you need to dynamically move a widget into a different cell. Example 26-3 keeps track of rows and columns explicitly and achieves the same layout as Example 26-2: Example 26-3 A grid with row and column specifications set row 0 foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 grid .l$color -row $row -column 0 -sticky w grid .f$color -row $row -column 1 -sticky ns incr row } External Padding with -padx and -pady This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks You can keep a widget away from the edge of its cell with the -padx and -pady settings. Example 26-4 uses external padding to shift the labels away from the left edge, and to keep some blank space between the color bars: Example 26-4 A grid with external padding foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 -height 2 grid .l$color .f$color grid .l$color -sticky w -padx 3 grid .f$color -sticky ns -pady 1 } Tk 8.4 added the ability to specify asymmetric padding as a list of two screen distances. For example, -padx {0.125i 0.25i} adds 1/8 inch of padding to the left and 1/4 inch padding to the right of a widget. Internal Padding with -ipadx and -ipady You can give a widget more display space than it normally needs with internal padding. The internal padding increases the size of the grid. In contrast, a -sticky setting might stretch a widget, but it will not change the size of the grid. Example 26-5 makes the labels taller with-ipady: Example 26-5 A grid with internal padding This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks foreach color {red orange yellow green blue purple} { label .l$color -text $color -bg white frame .f$color -background $color -width 100 -height 2 grid .l$color .f$color grid .l$color -sticky w -padx 3 -ipady 5 grid .f$color -sticky ns -pady 1 } Multiple Widgets in a Cell Example 26-6 shows all possible -sticky settings. It uses the ability to put more than one widget into a grid cell. A large square frame is put in each cell, and then a label is put into the same cell with a different -sticky setting. It is important to create the frame first so it is below the label. Window stacking is discussed on page 409. External padding is used to keep the labels away from the edge so that they do not hide the -ridge relief of the frames. Example 26-6 All combinations of -sticky settings This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. set index 0 foreach x {news ns ew " " new sew wsn esn nw ne sw se n s w e} { frame .f$x -borderwidth 2 -relief ridge -width 40 -height 40 grid .f$x -sticky news \ -row [expr {$index/4}] -column [expr {$index%4}] label .l$x -text $x -background white grid .l$x -sticky $x -padx 2 -pady 2 \ -row [expr {$index/4}] -column [expr {$index%4}] incr index } [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Spanning Rows and Columns A widget can occupy more than one cell. The -rowspan and -columnspan attributes indicate how many rows and columns are occupied by a widget. Example 26-7 uses explicit row, column, rowspan, and columnspan specifications: Example 26-7 Explicit row and column span . config -bg white foreach color {888 999 aaa bbb ccc fff} { frame .$color -bg #$color -width 40 -height 40 } grid .888 -row 0 -column 0 -columnspan 3 -sticky news grid .999 -row 1 -column 0 -rowspan 2 -sticky news grid .aaa -row 1 -column 1 -columnspan 2 -sticky news grid .bbb -row 2 -column 2 -rowspan 2 -sticky news grid .ccc -row 3 -column 0 -columnspan 2 -sticky news grid .fff -row 2 -column 1 -sticky news You can also use special syntax in grid commands that imply row and column placement. Special characters represent a cell that is spanned or skipped: - represents a spanned column. ^ represents a spanned row. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks x represents a skipped cell. A nice feature of the implicit row and column assignments is that it is easy to make minor changes to your layout. Example 26-8 achieves the same layout: Example 26-8 Grid syntax row and column span . config -bg white foreach color {888 999 aaa bbb ccc ddd fff} { frame .$color -bg #$color -width 40 -height 40 } grid .888 - -sticky news grid .999 .aaa -sticky news grid ^ .fff .bbb -sticky news grid .ccc - ^ -sticky news [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Row and Column Constraints The grid manager supports attributes on whole rows and columns that affect their size and resize behavior. Thegrid command has a rowconfigure and columnconfigure operation to set and query these attributes: grid columnconfigure master col ?attributes? grid rowconfigure master row ?attributes? With no attributes, the current settings are returned. Therow and col specifications can be lists instead of simple indices, so you can configure several rows or columns at once. Row and Column Padding The -pad attribute increases a row or column size. The initial size of a row or column is determined by the largest widget, and -pad adds to this size. This padding can be filled by the widget by using the -sticky attribute. Row and column padding works like internal padding because it is extra space that can be occupied by the widget's display. In contrast, the -padx and -pady attributes on an individual widget act like a spacer that keeps the widget away from the edge of the cell. Example 26-9 shows the difference. The row padding increases the height of the row, but the padding on .f1 keeps it away from the edge of the cell: Example 26-9 Row padding compared to cell padding . config -bg black label .f1 -text left -bg #ccc label .f2 -text right -bg #aaa . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks grid .f1 .f2 -sticky news ;# no padding grid .f1 -padx 10 -pady 10 ;# cell padding grid rowconfigure . 0 -pad 20 ;# row padding Minimum Size The -minsize attribute restricts a column or row to be a minimum size. The row or column can grow bigger if its widget requests it, but they will not get smaller than the minimum. One useful application of -minsize is to create empty rows or columns, which is more efficient than creating an extra frame. Managing Resize Behavior If the master frame is bigger than the required size of the grid, it shrinks to be just large enough to contain the grid. You can turn off the shrink-wrap behavior with grid propagate. If geometry propagation is off, then the grid is centered inside the master. If the master frame is too small to fit the grid, then the grid is anchored to the upper-left corner of the master and clipped on the bottom-right. By default, rows and columns do not resize when you grow the master frame. You enable resizing by specifying a -weight for a row or column that is an integer value greater than zero. Example 26-10 grids a text widget and two scrollbars. The protocol between thescrollbar and the text widget is described on page 501. The text widget is in row 0, column 0, and both of these can expand. The vertical scrollbar is in row 0, column 1, so it only grows in the Y direction. The horizontal scrollbar is in row 1, column 0, so it only grows in the X direction: Example 26-10 Gridding a text widget and scrollbar text .text -yscrollcommand ".yscroll set" \ -xscrollcommand ".xscroll set"-width 40 -height 10 scrollbar .yscroll -command ".text yview" -orient vertical This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks scrollbar .xscroll -command ".text xview" -orient horizontal grid .text .yscroll -sticky news grid .xscroll -sticky ew grid rowconfigure . 0 -weight 1 grid columnconfigure . 0 -weight 1 You can use different weights to let different rows and columns grow at different rates. However, there are some tricky issues because the resize behavior applies to extra space, not total space. For example, suppose there are four columns that have widths 10, 20, 30, and 40 pixels, for a total of 100. If the master frame is grown to 140 pixels wide, then there are 40 extra pixels. If each column has weight 1, then each column gets an equal share of the extra space, or 10 more pixels. Now suppose column 0 has weight 0, columns 1 and 2 have weight 1, and column 3 has weight 2. Column 0 will not grow, columns 1 and 2 will get 10 more pixels, and column 3 will get 20 more pixels. In most cases, weights of 0 or 1 make the most sense. Weight works in reverse when shrinking. If a row or column has to shrink, the weights are applied in reverse. A row or column with a higher weight will shrink more. For example, put two equal sized frames in columns with different weights. When the user makes the window bigger, the frame in the column with more weight gets larger more quickly. When the window is made smaller, that frame gets smaller more quickly. Uniform Columns The -uniform attribute makes it easy to create columns (or rows) that are the same width (or height). Use the-uniform attribute to create a group of columns (or rows). The value of the attribute can by anything (e.g., xyz). All columns (or rows) with the same-uniform attribute are in the same group. If they all have the same -weight value, then they are all the same size. If one column (or row) in a group has-weight a that is twice what the other columns (or rows) have, then it is twice as big. This is illustrated in Example 26-11. Example 26-11 Uniform column width foreach x {alpha beta gamma x y z} { label .$x -text $x } .beta config -bg white .y config -bg white grid .alpha .beta .gamma -sticky news grid .x .y .z -sticky news . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks grid columnconfigure . "0 1 2" -uniform group1 -weight 1 grid columnconfigure . 1 -weight 2 [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The grid Command Table 26-1 summarizes the usage of the grid command. Table 26-2 summarizes the options for a widget set with thegrid configure command. Table 26-1. The grid command grid bbox master ?c1 r1? ?c2 r2? Returns the bounding box, of the whole grid, the cell atc1, r1, or the cells from c1, r1 to c2, r2. grid columnconfigure master col ?options? Sets or queries the configuration of col. Options are-minsize, -weight, -pad, and -uniform. grid configure win ?win ...? ?options? Grids one or more widgets according to theoptions, which are given inTable 26-2. grid forget win ?win...? Unmaps the specified windows. grid info win Returns the grid options of win. grid location master x y Returns the cell column and row under the pointx, y in master. grid propagate master ?boolean? Enables or disables shrink-wrapping of master. grid rowconfigure master row ?options? Sets or queries the configuration of row. Options are-minsize, -weight, -pad, and -uniform. grid remove slave Unmaps slave, but remember its configuration. grid size master Returns the number of columns and rows. grid slaves win ?-row r? ?-column c? Returns the list of widgets managed bywin, or just those in the specified row or column. Table 26-2. Grid widget options -in win Places inside win. -column col Column position. Columns count from zero. -columnspan n Spans n columns. -ipadx pixels Internal widget padding in the X direction, in screen units. -ipady pixels Internal widget padding in the Y direction, in screen units. -padx pixels External widget padding in the X direction, in screen units. May be a list of two screen units for asymmetric padding (Tk 8.4). This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks -pady pixels External widget padding in the Y direction, in screen units. May be a list of two screen units for asymmetric padding (Tk 8.4). -row row Row position. Rows count from zero. -rowspan n Spans n rows. -sticky how Positions widget next to any combination of north (n), south (s), east (e), and west (w) sides of the cell. Use {} for center. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 27. The Place Geometry Manager This chapter explores the place geometry manager that positions widgets on the screen. The place geometry manager is much simpler thanpack and grid. You specify the exact position and size of a window, or you specify the relative position and relative size of a widget. This is useful in a few situations, but it rapidly becomes tedious if you have to position lots of windows. The best application of place is to create special-purpose geometry managers using its relative constraints. A standard application of place is to adjust the boundary between two adjacent windows. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. place Basics The place command lets you specify the width and height of a window, and the X and Y locations of the window's anchor point. The size and location can be specified in absolute or relative terms. Relative specifications are more powerful. Example 27-1 uses place to center a window in its parent. You can use this command to position dialogs that you do not want to be detached top-level windows: Example 27-1 Centering a window with place place $w -in $parent -relx 0.5 -rely 0.5 -anchor center The -relx and -rely specify the relative X and Y positions of the anchor point of the widget$w in $parent. A relative X (or Y) value of zero corresponds to the left (or top) edge of $parent. A value of one corresponds to the right (or bottom) edge of$parent. A value of 0.5 specifies the middle. The anchor point determines what point in $w is positioned according to the specifications. In Example 27-1 the center anchor point is used so that the center of $w is centered in $parent. The relative height and width settings are used to base a widget's size on another widget. Example 27-2 completely covers one window with another window. It uses the default anchor point for windows, which is their upper-left hand corner (nw): Example 27-2 Covering a window with place place $w -in $parent -relwidth 1 -relheight 1 -x 0 -y 0 The absolute and relative size and position parameters are additive (e.g., -width and -relwidth). You can make a window slightly larger or smaller than the parent by specifying both parameters. In Example 27-3, a negative width and height are used to make a window smaller than another one: Example 27-3 Combining relative and absolute sizes place $w -in $parent -relwidth 1 -relheight 1 -x 0 -y 0 \ -width -4 -height -4 It is not necessary for $parent to actually be the parent widget of $w. The requirement is that $parent be the parent, or a descendant of the parent, of $w. It also has to be in the same top-level window. This guarantees that$w is visible whenever $parent is visible. These are the same restrictions imposed by the pack geometry manager. It is not necessary to position a widget inside another widget, either. Example 27-4 positions a window five pixels above a sibling widget. If $sibling is repositioned, then $w moves with it. This approach is useful when you decorate a resizable window by placing other widgets at its corners or edges. When the window is resized, the decorations automatically move into place: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Example 27-4 Positioning a window above a sibling with place place $w -in $sibling -relx 0.5 -y -5 -anchor s \ -bordermode outside The -bordermode outside option is specified so that any decorative border in$sibling is ignored when positioning $w. In this case the position is relative to the outside edge of $sibling. By default, the border is taken into account to make it easy to position widgets inside their parent's border. The parent widget does not have to be a frame. Example 27-1 can be used to place a dialog in the middle of atext widget. In Example 27-4, $sibling and $w can both be label widgets. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The Pane Manager The relative size and placement parameters of the place command can be used to create custom geometry managers.Example 27-5 shows a paned layout manager. Two frames, or panes, are placed inside another frame. A small third frame represents a grip that is used to adjust the boundary between the two panes. Note that Tk 8.4 added apanedwindow widget, which can manage an arbitrary number of horizontal or vertical panes. See Chapter 28 for information on how to use the panedwindow widget. Example 27-5 Pane_Create sets up vertical or horizontal panes proc Pane_Create {f1 f2 args} { # Map optional arguments into array values set t(-orient) vertical set t(-percent) 0.5 set t(-in) [winfo parent $f1] array set t $args # Keep state in an array associated with the master frame set master $t(-in) upvar #0 Pane$master pane array set pane [array get t] # Create the grip and set placement attributes that # will not change. A thin divider line is achieved by # making the two frames one pixel smaller in the This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks # adjustable dimension and making the main frame black. set pane(1) $f1 set pane(2) $f2 set pane(grip) [frame $master.grip -background gray50 \ -width 10 -height 10 -bd 1 -relief raised \ -cursor crosshair] if {[string match vert* $pane(-orient)]} { set pane(D) Y;# Adjust boundary in Y direction place $pane(1) -in $master -x 0 -rely 0.0 -anchor nw \ -relwidth 1.0 -height -1 place $pane(2) -in $master -x 0 -rely 1.0 -anchor sw \ -relwidth 1.0 -height -1 place $pane(grip) -in $master -anchor c -relx 0.8 } else { set pane(D) X ;# Adjust boundary in X direction place $pane(1) -in $master -relx 0.0 -y 0 -anchor nw \ -relheight 1.0 -width -1 place $pane(2) -in $master -relx 1.0 -y 0 -anchor ne \ -relheight 1.0 -width -1 place $pane(grip) -in $master -anchor c -rely 0.8 } $master configure -background black # Set up bindings for resize, <Configure>, and # for dragging the grip. bind $master <Configure> [list PaneGeometry $master] bind $pane(grip) <ButtonPress-1> \ [list PaneDrag $master %$pane(D)] bind $pane(grip) <B1-Motion> \ [list PaneDrag $master %$pane(D)] bind $pane(grip) <ButtonRelease-1> \ [list PaneStop $master] # Do the initial layout PaneGeometry $master } Parsing Arguments and Maintaining State The Pane_Create procedure is given two widgets to manage, and an optional set of parameters. The general syntax ofPane_Create is: Pane_Create f1 f2 ?-orient xy? ?-percent p? ?-in master? All the optional arguments are available in $args. Its attribute-value structure is used to initialize a temporary arrayt. Default values are set before the assignment from $args. The following code is compact but doesn't check errors in the optional arguments. set t(-orient) vertical set t(-percent) 0.5 set t(-in) [winfo parent $f1] array set t $args This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Global state about the layout is kept in an array whose name is based on the master frame. The name of the master frame isn't known until after arguments are parsed, which is why t is used. After the upvar the argument values are copied from the temporary array into the global state array: set master $t(-in) upvar #0 Pane$master pane array set pane [array get t] Sticky Geometry Settings Example 27-5 sets several place parameters on the frames when they are created. These are remembered, and other parameters are adjusted later to dynamically adjust the boundary between the frames. All Tk geometry managers retain settings like this. The initial settings for the vertical layout is shown here: place $pane(1) -in $parent -x 0 -rely 0.0 -anchor nw \ -relwidth 1.0 -height -1 place $pane(2) -in $parent -x 0 -rely 1.0 -anchor sw \ -relwidth 1.0 -height -1 place $pane(grip) -in $parent -anchor c -relx 0.8 The position of the upper and lower frames is specified with an absolute X and a relative Y position, and the anchor setting is chosen to keep the frame visible inside the main frame. For example, the lower frame is positioned at the bottom-left corner of the container with -x 0 and -rely 1.0. The -anchor sw attaches the lower-left corner of the frame to this position. The size of the contained frames is also a combination of absolute and relative values. The width is set to the full width of the container with -relwidth 1.0. The height is set to minus one with -height -1. This value gets added to a relative height that is determined later. It will leave a little space between the two contained frames. The resize grip is just a small frame positioned at the boundary. Initially it is just placed over toward one size with -relx 0.8. It gets positioned on the boundary with a -rely setting later. It has a different cursor to indicate it is active. Event Bindings The example uses some event bindings that are described in more detail in Chapter 29. The <Configure> event occurs when the containing frame is resized by the user. When the user presses the mouse button over the grip and drags it, there is a <ButtonPress-1> event, one or more <B1-Motion> events, and finally a<ButtonRelease-1> event. Tcl commands are bound to these events: bind $parent <Configure> [list PaneGeometry $parent] bind $pane(grip) <ButtonPress-1> \ [list PaneDrag $parent %$pane(D)] bind $pane(grip) <B1-Motion> \ [list PaneDrag $parent %$pane(D)] bind $pane(grip) <ButtonRelease-1> [list PaneStop $parent] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Managing the Layout The code is set up to work with either horizontal or vertical layouts. The pane(D) variable is either X, for a horizontal layout, orY, for a vertical layout. This value is used in the bindings to get %X or %Y, which are replaced with the X and Y screen positions of the mouse when the bindings fire. This value is passed to PaneDrag as the parameter D. The PaneDrag procedure remembers the previous position inpane(lastD) and uses that to update the percentage split between the two contained panes: Example 27-6 PaneDrag adjusts the percentage proc PaneDrag {master D} { upvar #0 Pane$master pane if [info exists pane(lastD)] { set delta [expr double($pane(lastD) - $D) \ / $pane(size)] set pane(-percent) [expr $pane(-percent) - $delta] if {$pane(-percent) < 0.0} { set pane(-percent) 0.0 } elseif {$pane(-percent) > 1.0} { set pane(-percent) 1.0 } PaneGeometry $master } set pane(lastD) $D } proc PaneStop {master} { upvar #0 Pane$master pane catch {unset pane(lastD)} } The PaneGeometry procedure adjusts the positions of the frames. It is called when the main window is resized, so it updatespane(size). It is also called as the user drags the grip. For a vertical layout, the grip is moved by setting its relative Y position. The size of the two contained frames is set with a relative height. Remember that this is combined with the fixed height of -1 to get some space between the two frames: Example 27-7 PaneGeometry updates the layout proc PaneGeometry {master} { upvar #0 Pane$master pane if {$pane(D) == "X"} { place $pane(1) -relwidth $pane(-percent) place $pane(2) -relwidth [expr 1.0 - $pane(-percent)] place $pane(grip) -relx $pane(-percent) set pane(size) [winfo width $master] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks } else { place $pane(1) -relheight $pane(-percent) place $pane(2) -relheight [expr 1.0 - $pane(-percent)] place $pane(grip) -rely $pane(-percent) set pane(size) [winfo height $master] } } proc PaneTest {{p .p} {orient vert}} { catch {destroy $p} frame $p -width 200 -height 200 label $p.1 -bg blue -text foo label $p.2 -bg green -text bar pack $p -expand true -fill both pack propagate $p off Pane_Create $p.1 $p.2 -in $p -orient $orient -percent 0.3 } [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The place Command Table 27-1 summarizes the usage of the place command. Table 27-1. The place command place win ?win ..? ?options? This is just like place configure. place configure win ?win ...? ?options? Places one or more widgets according to theoptions, which are givenTable 27-2. place forget win ?win...? Unmaps the specified windows. place info win Returns the placement parameters of win. place slaves win Returns the list of widgets managed bywin. Table 27-2 summarizes the placement options for a widget, which you set with theplace configure command and retrieve with the place info command. Table 27-2. Placement options -in win Places inside (or relative to) win. -anchor where Anchors: center, n, ne, e, se, s, sw, w, or nw. Default: nw. -x coord X position, in screen units, of the anchor point. -relx offset Relative X position. 0.0 is the left edge. 1.0 is the right edge. -y coord Y position, in screen units, of the anchor point. -rely offset Relative Y position. 0.0 is the top edge. 1.0 is the bottom edge. -width size Width of the window, in screen units. -relwidth size Width relative to parent's width. 1.0 is full width. -height size Height of the window, in screen units. -relheight size Height relative to the parent's height. 1.0 is full height. -bordermode mode If mode is inside, then size and position are inside the parent's border. If mode isoutside, then size and position are relative to the outer edge of the parent. The default is inside. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 28. The Panedwindow Widget The panedwindow widget, introduced in Tk 8.4, displays widgets in resizable horizontal or vertical panes. A panedwindow contains any number of panes, arranged horizontally or vertically. Each pane contains one widget, and each pair of panes is separated by a moveable sash, which causes the widgets on either side of the sash to be resized. When a panedwindow is resized externally — for example, if the user resizes the toplevel containing the panedwindow — space is added or subtracted from the last pane (right-most or bottom-most pane) in the widget. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Using the Panedwindow The panedwindow is a relatively simple widget, requiring little configuration or programming in most applications. The most frequently used configuration attribute is orient, which determines whether the widget has ahorizontal or vertical arrangement of panes. The other frequently used configuration attribute is showHandle. The handle is a small square drawn on the sashes, giving users another visual cue that the sashes are interactive. The default value of showHandle is False on Windows to match its native look and feel. Most other configuration attributes control the size, positioning, and appearance of the handles, the sashes, and the widget in general. Manipulating the Pane Contents Once you've created the panedwindow, you add widgets to it with the add operation. You can add multiple widgets with a singleadd operation. The panedwindow displays each widget added in its own pane, separated by sashes. By default, the widgets are arranged in the order added. However, you can override this behavior with the -after and -before options to insert widgets after or before currently managed widgets. You can add horizontal and vertical padding to the widgets in the panes with -padx and -pady options, just like with other geometry managers. The -minsize attribute allows you to specify a minimum size for managed widgets (in any screen units supported by Tk.) You can also control the position of a widget within its pane with the -sticky attribute, which operates similarly to grid's -sticky attribute. The panedwindow's default -sticky setting is nsew, causing the managed widget to resize to completely fill its pane in both directions. Don't pack, grid, or place the widgets in a panedwindow. A panedwindow widget is not only a container for other widgets, but it is also a geometry manager. It controls the size and position of the widgets that it manages. Therefore, don't use the pack, grid, or place commands to control the widgets that you add to a panedwindow. Of course, for more complex interfaces, you can add frames as the managed widgets of a panedwindow, and then pack, grid, or place other widgets within those frames. As an example, consider a layout with two text widgets. We'd like each text widget to have horizontal and vertical scrollbars, which is a natural application of grid. But then we want the entire layout managed by a 2-pane vertical panedwindow. In this case, we'll use a labelframe widget to contain each gridded text-and-scrollbar assembly, and then add each labelframe as a managed widget of our panedwindow. The result is shown in Example 28-1. Example 28-1 A panedwindow with complex managed widgets . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Create the panedwindow to manage the entire display panedwindow .p -orient vertical -showhandle 1 pack .p -expand yes -fill both # Create 2 labelframe widgets, each containing a # gridded text and scrollbar assembly. foreach {w label} {code "Code:" notes "Notes:"} { set f [labelframe .p.$w -text $label] text $f.t -height 10 -width 40 \ -wrap none -font {courier 12} \ -xscrollcommand [list $f.xbar set] \ -yscrollcommand [list $f.ybar set] scrollbar $f.xbar -orient horizontal \ -command [list $f.t xview] scrollbar $f.ybar -orient vertical \ -command [list $f.t yview] grid $f.t -row 0 -column 0 -sticky news -padx 2 -pady 2 grid $f.ybar -row 0 -column 1 -sticky ns -padx 2 -pady 2 grid $f.xbar -row 1 -column 0 -sticky ew -padx 2 -pady 2 grid columnconfigure $f 0 -weight 1 grid rowconfigure $f 0 -weight 1 # Add the frame assembly to the panedwindow .p add $f -minsize 1i -padx 4 -pady 6 } The forget operation removes widgets from a panedwindow. The widgets aren't destroyed, but they are no longer managed by the paned window, and the pane they formerly occupied is removed from the panedwindow. You can also get a list of the widgets currently managed by . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks a panedwindow (in the order in which they appear) with the panes operation. Create the managed widgets as children of the panedwindow. For best results, create the widgets managed by a panedwindow as children of that panedwindow. Tk then automatically handles the stacking order for windows so that the child appears on top of the panedwindow. If you don't create the managed widget as a child of the panedwindow, you either need to create the managed widget after the panedwindow, or else use the raise command to raise the managed widget above the panedwindow, as discussed in "Window Stacking Order" on page 409. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Programming Panedwindow Widgets Table 28-1 summarizes the operations for programming a panedwindow. In the table,$w is a panedwindow widget and win is a widget managed by the panedwindow. Table 28-1. Panedwindow operations $w add win ?win...? ?option value...? Adds one or more widgets to the panedwindow, each in a separate pane, with options as described in Table 28-2. $w cget option Returns the value of the configuration option as described in Table 28-3. $w configure ?option value...? Queries or modifies the panedwindow configuration, with options as described in Table 28-3. $w forget win ?win...? Removes the pane(s) containing widget(s) from the panedwindow. $w identify x y Identifies the panedwindow component underneath the specified point. $w panecget win option Returns the value of the widget's configuration option as described in Table 28-2. $w paneconfigure index ?option? ?value? ?...? Queries or modifies the widget's configuration options as described in Table 28-2. $w panes Returns an ordered list of the widgets managed by the panedwindow. $w proxy coord Returns the current x and y coordinate pair for the sash proxy, used for rubberband-style pane resizing. $w proxy forget Removes the sash proxy from the display. $w proxy place x y Places the sash proxy at the given coordinates. $w sash coord index Returns the current x and y coordinate pair for the sash indicated byindex. $w sash dragto index x y Moves the sash from the previous mark position. $w sash mark index x y Starts a sash movement operation. index is the sash to move, and x and y are widget-relative screen coordinates. $w sash place index x y Places the indicated sash at the given coordinates. Table 28-2 summarizes the panedwindow options for managed widgets. These are set when adding a widget to the paned window with the add operation or afterwards with paneconfigure operation. The current settings are returned by thepanecget operation. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 28-2. Panedwindow managed widget options -after win Inserts the widget after the specifiedwin. -before win Inserts the widget before the specified win. -height size Height of the widget, including its border, in screen units. The actual widget height may vary based on -sticky settings, -minsize size Minimum widget size of the widget in the paned dimension, specified in screen units. -padx size External widget padding in the X direction, in screen units. -pady size External widget padding in the Y direction, in screen units. -sticky how Positions the widget next to any combination of north (n), south (s), east (w), and west (e) sides of the pane. Use {} for center. -width size Width of the widget, including its border, in screen units. The actual widget width may vary based on -sticky settings, panedwindow resizing, and sash movement. If opposing directions are specified (e.g., ns), the widget stretches to fill in those directions. Default:nsew. panedwindow resizing, and sash movement. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Panedwindow Attributes Table 28-3 lists the panedwindow widget attributes. The table uses the resource name for the attribute, which has capitals at internal word boundaries. In Tcl commands these options are specified with a dash and all lowercase. Table 28-3. Panedwindow attributes background Background color (also bg). borderWidth Extra space around the edge of the widget, in screen units. cursor Cursor to display when mouse is over the widget. handlePad When sash handles are drawn, the distance in screen units from the top or left end of the sash (depending on the orientation) at which to draw the handle. handleSize The size of a sash handle, in screen units. Handles are always drawn as squares. height Height of the widget in screen units. opaqueResize Boolean. True indicates the panes should resize as a sash is moved. False (default) indicates resizing is deferred until the sash is placed. orient horizontal or vertical relief flat, sunken, raised, groove, solid, or ridge. sashCursor Cursor to display over a sash. Defaults to a double-sided arrow. sashPad Padding on both sides of a sash. sashRelief Relief style for sashes.flat, sunken, raised (default), groove, solid, or ridge. sashWidth Width of each sash, in screen units. showHandle Boolean, whether or not to show the sash handles. Defaults to False on Windows, and True on other platforms. width Width of the widget in screen units. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 29. Binding Commands to Events This chapter introduces the event binding mechanism in Tk. Bindings associate a Tcl command with an event like a mouse click or a key stroke. There are also facilities to define virtual events like <<Cut>> and <<Paste>> that are associated with different keystrokes on different platforms. Tcl commands discussed are: bind, bindtags, and event. Bindings associate a Tcl command with a sequence of events from the window system. Events include key press, key release, button press, button release, mouse entering a window, mouse leaving, window changing size, window open, window close, focus in, focus out, and widget destroyed. The bindings are defined on binding tags, and each widget is associated with an ordered set of binding tags. The binding tags provide a level of indirection between bindings and widgets that creates a flexible and powerful system. Virtual events are used to support a different look and feel on different platforms. A virtual event is a higher-level name, like <<Copy>>, for a lower-level event name like <Control-c> or <Key-F6>. A virtual event hides the different keystrokes used on different platforms for the same logical operation. Tk defines a few virtual events, and applications can define their own. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. [ Team LiB ] The bind Command The bind command creates event bindings, and it returns information about current bindings. The general form of the command is: bind bindingTag ?eventSequence? ?command? If all arguments are present, a binding from eventSequence to command is defined for bindingTag. The bindingTag is typically a widget class name (e.g., Button) or a widget instance name (e.g., .buttons.foo). Binding tags are described in more detail later. Called with a single argument, a binding tag, bind returns the events for which there are command bindings: bind Menubutton => <Key-Return> <Key-space> <ButtonRelease-1> <B1-Motion> <Motion> <Button-1> <Leave> <Enter> The events in this example are keystroke and mouse events. <Button-1> is the event generated when the user presses the first, or left-hand, mouse button. <B1-Motion> is generated when the user moves the mouse while holding down the first mouse button. The<Key-space> event occurs when the user presses the space bar. The surrounding angle brackets delimit a single event, and you can define bindings for a sequence of events. The event syntax is described on page 439, and event sequences are described on page 445. If bind is given a binding tag and an event sequence, it returns the Tcl command bound to that event sequence: bind Menubutton <B1-Motion> => tk::MbMotion %W down %X %Y The Tcl commands in event bindings support an additional syntax for event keywords. These keywords begin with a percent sign and have one more character that identifies some attribute of the event. The keywords are substituted with event-specific data before the Tcl command is evaluated. For example, %W is replaced with the widget's pathname. The %X and %Y keywords are replaced with the coordinates of the event relative to the screen. The %x and %y keywords are replaced with the coordinates of the event relative to the widget. Theevent keywords are summarized on page 448. The % substitutions are performed throughout the entire command bound to an event, without regard to other quoting schemes. You must use %% to obtain a single percent sign. For this reason you should make your binding commands short, adding a new procedure if necessary (e.g., tk::MbMotion), instead of littering percent signs throughout your code. A new binding is created by specifying a binding tag, an event sequence, and a command: bind Menubutton <B1-Motion> {tk::MbMotion %W down %X %Y} If the first character of the binding command is +, the command (without the +) is added to the commands, if any, for that event and binding tag: bind bindingTag event {+ command args} To delete a binding for an event, bind the event to the null string: bind bindingTag event {} Bindings execute in the global scope. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks When a binding is triggered, the command is evaluated at the global scope. A very common mistake is to confuse the scope that is active when the bind command creates a binding, and the scope that is active when the binding is triggered. The same problem crops up with the commands associated with buttons, and it is discussed in more detail at the beginning of Chapter 30. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] The bindtags Command A binding tag groups related bindings, and each widget is associated with an ordered set of binding tags. The level of indirection between widgets and bindings lets you group functionality on binding tags and compose widget behavior from different binding tags. For example, the all binding tag has bindings on <Tab> that change focus among widgets. TheText binding tag has bindings on keystrokes that insert and edit text. Only text widgets use the Text binding tag, but all widgets share theall binding tag. You can introduce new binding tags and change the association of widgets to binding tags dynamically. The result is a powerful and flexible way to manage bindings. The bindtags command sets or queries the binding tags for a widget. The general form of thebindtags command is: bindtags widget ?tagList? The following command returns the binding tags for text widget .t: bindtags .t => .t Text . all You can change the binding tags and their order. The tagList argument to bindtags must be a proper Tcl list. The following command reorders the binding tags for .t and eliminates the . binding tag: bindtags .t [list all Text .t] By default, all the Tk widgets, except a toplevel, have four binding tags in the following order: The widget's Tk pathname (e.g., .t). Use this binding tag to provide special behavior to a particular widget. There are no bindings on this bindtag by default. The widget's class (e.g., Text). The class for a widget is derived from the name of the command that creates it. A button widget has the class Button, a text has the classText, and so on. The Tk widgets define their default behavior with bindings on their class. The Tk pathname of the widget's toplevel window (e.g., .). This is redundant in the case of a toplevel widget, so it is not used twice. There are no bindings on this bindtag by default. The bindings on a toplevel window can be used in dialog boxes to handle keyboard accelerators. The global binding tag all. The default bindings on all are used to change focus among widgets. They are described on page 604. When there is more than one binding tag on a widget, then one binding from each binding tag can match an event. The bindings are processed in the order of the binding tags. By default, the most specific binding tag comes first, and the most general binding tag comes last. Example 29-1 has two frame widgets that have the following behavior. When the mouse enters them, they turn red. They turn white when the mouse leaves. When the user types <Control-c>, the frame under the mouse is destroyed. One of the frames,.two, reports the coordinates of mouse clicks. Example 29-1 Bindings on different binding tags This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks frame .one -width 30 -height 30 frame .two -width 30 -height 30 bind Frame <Enter> {%W config -bg red} bind Frame <Leave> {%W config -bg white} bind .two <Button> {puts "Button %b at %x %y"} pack .one .two -side left bind all <Control-c> {destroy %W} bind all <Enter> {focus %W} The Frame class has a binding on <Enter> and <Leave> that changes a frame's background color when the mouse moves in and out of the window. This binding is shared by all the frames. There is also a binding on all for <Enter> that sets the keyboard focus. Both bindings will trigger when the mouse enters a frame. Focus and Key Events The binding on <Control-c> is shared by all widgets. The binding destroys the target widget. Because this is a keystroke, it is important to get the keyboard focus directed at the proper widget. By default, focus is on the main window, and destroying it terminates the entire application. The global binding for <Enter> gives focus to a widget when you move the mouse over the widget. In this example, moving the mouse into a widget and then typing <Control-c> destroys the widget. Bind thefocus command to <Button> instead of <Enter> if you prefer a click-to-type focus model. Focus is described in Chapter 39. Using break and continue in Bindings The break and continue commands control the progression through the set of binding tags. Thebreak command stops the current binding and suppresses the bindings from any remaining tags in the binding set order. The continue command in a binding stops the current binding and continues with the command from the next binding tag. For example, the Entry binding tag has bindings that insert and edit text in a one-line entry widget. You can put a binding on <Return> that executes a Tcl command using the value of the widget. The following example runs Some Command before the \r character is added to the entry widget. The binding is on the name of the widget, which is first in the set of binding tags, so the break suppresses the Entry binding that inserts the character: bind .entry <Return> {Some Command ; break} Note that you cannot use the break or continue commands inside a procedure that is called by the binding. This is because the procedure mechanism will not propagate the break or continue signal. Instead, you could use the-code option to return, which is described on page 86: return -code break This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Defining New Binding Tags You introduce new binding tags just by using them in a bind or bindtags command. Binding tags are useful for grouping bindings into different sets, such as specialized bindings for different modes of an editor. One way to emulate the vi editor, for example, is to use two bind tags, one for insert mode and one for command mode. The user types i to enter insert mode, and they type<Escape> to enter command mode: bindtags $t [list ViInsert Text $t all] bind ViInsert <Escape> {bindtags %W {ViCmd %W all}} bind ViCmd <Key-i> {bindtags %W {ViInsert Text %W all}} The Text class bindings are used in insert mode. The command to put the widget into command mode is put on a new binding tag, ViInsert, instead of changing the default Text bindings. The bindtag command changes the mode by changing the set of binding tags for the widget. The %W is replaced with the name of the widget, which is the same as$t in this example. Of course, you need to define many more bindings to fully implement all the vi commands. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Event Syntax The bind command uses the following syntax to describe events: <modifier-modifier-type-detail> <<Event>> The first form is for physical events like keystrokes and mouse motion. The second form is for virtual events like Cut and Paste, which correspond to different physical events on different platforms. Physical events are described in this section. Virtual events are described in more detail on page 446. The primary part of the description is the type, (e.g., Button or Motion). The detail is used in some events to identify keys or buttons, (.e.g., Key-a or Button-1). A modifier is another key or button that is already pressed when the event occurs, (e.g.,Control-Key-a or B2-Motion). There can be multiple modifiers (e.g., Control-Shift-x). The < and > delimit a single event. Table 29-1 lists all physical event types. When two event types are listed together (e.g.,ButtonPress and Button) they are equivalent. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 29-1. Event types Activate The application has been activated. (Macintosh) ButtonPress, Button A button is pressed (down). ButtonRelease A button is released (up). Circulate The stacking order of the window changed. CirculateRequest An application request to change its window stacking order. (Used by window managers.) Colormap The color map has changed. Configure The window changed size, position, border, or stacking order. ConfigureRequest An application request to change its window configuration. (Used by window managers.) Create An application request to create a window. (Used by window managers.) Deactivate The application has been deactivated. (Macintosh) Destroy The window has been destroyed. Enter The mouse has entered the window. Expose The window has been exposed. FocusIn The window has received focus. FocusOut The window has lost focus. Gravity The window has moved because of a change in size of its parent window. KeyPress, Key A key is pressed (down). KeyRelease A key is released (up). Leave The mouse is leaving the window. Map The window has been mapped (opened). MapRequest An application request to map a window. (Used by window managers.) Motion The mouse is moving in the window. MouseWheel The scrolling mouse wheel has moved. Property A property on the window has been changed or deleted. Reparent A window has been reparented. ResizeRequest An application request to resize window. (Used by window managers.) Unmap The window has been unmapped (iconified). Visibility The window has changed visibility. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Keyboard Events The KeyPress type is distinguished from KeyRelease so that you can have different bindings for each of these events.KeyPress can be abbreviated Key, and Key can be left off altogether if a detail is given to indicate what key. Finally, as a special case for KeyPress events, the angle brackets can also be left out. The following are all equivalent event specifications: <KeyPress-a> <Key-a> <a> a The detail for a key is also known as the keysym, which refers to the graphic printed on the key of the keyboard. For punctuation and non-printing characters, special keysyms are defined. Case is significant in keysyms, but unfortunately there is no consistent scheme. In particular BackSpace has a capital B and a capital S. Commonly encountered keysyms include:Return, Escape, BackSpace, Tab, Up, Down, Left, Right, comma, period, dollar, asciicircum, numbersign, exclam. Starting in Tk 8.3.2, the online documentation includes a newkeysym reference page that documents all standard keysyms. Finding out what keysyms are generated by your keyboard. There are times when you do not know what keysym is generated by a special key on your keyboard. The keysyms are defined by the window system implementation, and on UNIX systems they are affected by a dynamic keyboard map, the X modmap. You may find the next binding useful to determine just what the keysym for a particular key is on your system: bind $w <KeyPress> {puts stdout {%%K=%K %%A=%A}} The %K keyword is replaced with the keysym from the event. The %A is replaced with the printing character that results from the event and any modifiers like Shift. The %% is replaced with a single percent sign. Note that these substitutions occur in spite of the curly braces used for grouping. If the user types a capital Q, there are twoKeyPress events, one for the Shift key, and one for theq key. The output is: %K=Shift_R %A={} %K=Q %A=Q The Shift_R keysym indicates the right-hand shift key was pressed. The%A keyword is replaced with {} when modifier keys are pressed. You can check for this in <KeyPress> bindings to avoid doing anything if only a modifier key is pressed. On Macintosh, there is no event at all when the modifier keys are pressed. The following can be used with a text widget. The double quotes are necessary to force a string comparison: bind $w <KeyPress> { if {"%A" != "{}"} {%W insert insert %A} } Mouse Events This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Button events also distinguish between ButtonPress, (or Button), and ButtonRelease. Button can be left off if a detail specifies a button by number. The following are equivalent: <ButtonPress-1> <Button-1> <1> Note: The event <1> implies a ButtonPress event, while the event 1 implies a KeyPress event. To avoid confusion, always specify theKey or Button type. The mouse is tracked by binding to the Enter, Leave, and Motion events. Enter and Leave are triggered when the mouse comes into and exits out of the widget, respectively. A Motion event is generated when the mouse moves within a widget. The coordinates of the mouse event are represented by the %x and %y keywords in the binding command. The coordinates are widget-relative, with the origin at the upper-left hand corner of a widget's window. The keywords %X and %Y represent the coordinates relative to the screen: bind $w <Enter> {puts stdout "Entered %W at %x %y"} bind $w <Leave> {puts stdout "Left %W at %x %y"} bind $w <Motion> {puts stdout "%W %x %y"} A mouse drag event is a Motion event that occurs when the user holds down a mouse button. In this case the mouse button is modifier, a which is discussed in more detail on page 443. The binding looks like this: bind $w <B1-Motion> {puts stdout "%W %x %y"} Other Events The <Map> and <Unmap> events are generated when a window is opened and closed, or when a widget is packed or unpacked by its geometry manager. The <Activate> and <Deactivate> events are generated when an application is activated by the operating system. This applies to Macintosh systems, and it occurs when the user clicks in the application window. The <Configure> event is generated when the window changes size. A canvas that computes its display based on its size can bind a redisplay procedure to the <Configure> event, for example. The <Configure> event can be caused by interactive resizing. It can also be caused by a configure widget command that changes the size of the widget. You should not reconfigure a widget's size while processing a <Configure> event to avoid an indefinite sequence of these events. The <Destroy> event is generated when a widget is destroyed. You can intercept requests to delete windows, too. See also the description of the wm command on page 657. The <MouseWheel> event is generated on Windows by the small scrolling wheel built into the Microsoft Mouse. It reports a delta value using the %D keyword. Currently the delta is an integer multiple of 120, where positive values indicate a scroll up, and negative values indicate a scroll down. Note that most Unix systems don't report <MouseWheel> events, but some do report mousewheel movement via <ButtonPress-4> and <ButtonPress-5> events. Chapter 39 presents some examples that use the<FocusIn> and <FocusOut> events. The remaining events in Table 29-1 have to do with dark corners of the X protocol, and they are seldom used. More information can be found on these events in the Event Reference section of the Xlib Reference Manual (Adrian Nye, O'Reilly & Associates, Inc., 1992). This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Bindings on Top-level Windows Bindings on toplevels are shared by widgets they contain. Be careful when binding events to toplevel windows because their name is used as a binding tag on all the widgets contained in them. For example, the following binding fires when the user destroys the main window, which means the application is about to exit: bind . <Destroy> {puts "goodbye"} Unfortunately, all widgets inside the main window are destroyed as a side effect, and they all share the name of their toplevel widget as a binding tag. So this binding fires when every widget inside the main window is destroyed. Typically you only want to do something one time. The following binding checks the identity of the widget before doing anything: bind . <Destroy> {if {"%W" == "."} {puts "goodbye"}} [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks [ Team LiB ] Modifiers A modifier indicates that another key or button is being held down at the time of the event. Typical modifiers are the Shift and Control keys. The mouse buttons can also be used as modifiers. If an event does not specify any modifiers, the presence of a modifier key is ignored by the event dispatcher. However, if there are two possible matching events, the more accurate match will be used. For example, consider these three bindings: bind $w <KeyPress> {puts "key=%A"} bind $w <Key-c> {puts "just a c"} bind $w <Control-Key-c> {exit} The last event is more specific than the others. Its binding will be triggered when the user types c with the Control key held down. If the user types c with the Meta key held down, the second binding will be triggered. TheMeta key is ignored because it does not match any binding. If the user types something other than a c, the first binding is triggered. If the user presses theShift key, then the keysym that is generated isC, not c, so the last two events do not match. There are eight possible modifier keys. The Control, Shift, and Lock modifiers are found on nearly all keyboards. TheMeta and Alt modifiers tend to vary from system to system, and they may not be defined at all. They are commonly mapped to be the same as Mod1 or Mod2, and Tk will try to determine how the mappings are set. The Macintosh has a Command modifier that corresponds to the clover-leaf or apple key. The remaining modifiers, Mod3 through Mod5, are sometimes mapped to other special keys. In OpenLook environments, for example, the Paste function key is also mapped to the Mod5 modifier. The button modifiers, B1 through B5, are most commonly used with theMotion event to distinguish different mouse dragging operations. For example, <B1-Motion> is the event generated when the user drags the mouse with the first mouse button held down. Double-click warning. The Double, Triple, and Quadruple events match on repetitions of an event within a short period of time. These are commonly used with mouse events. Be careful: The binding for the regular press event will match on the first press of the Double. Then the command bound to the Double event will match on the second press. Similarly, aDouble event will match on the first two presses of aTriple event, and so on. Verify this by trying out the following bindings: bind . <1> {puts stdout 1} bind . <Double-1> {puts stdout 2} bind . <Triple-1> {puts stdout 3} If you click the first mouse button several times quickly, you will see a 1, 2, and then a few3's output. Your bindings must take into consideration that more than one binding might match a Double, Triple, or Quadruple event. This effect is compatible with an interface that selects an object with the first click, and then operates on the selected object with a Double event. In an editor, character, word, line, and This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com . to register it. Thanks [*] paragraph selection on a single, double, triple, and quadruple click, respectively, is a good example. [*] If you really want to disable this, you can experiment with usingafter to postpone processing of one event. The time constant in the bind implementation of <Double> is 500 milliseconds. At the single-click event, schedule its action to occur after 600 milliseconds, and verify at that time that the <Double> event has not occurred. Table 29-2 summarizes the modifiers. Table 29-2. Event modifiers Control The control key. Shift The shift key. Lock The caps-lock key. Command The command key. (Macintosh) Meta, M Defined to be what ever modifier (M1 through M5) is mapped to the Meta_L and Meta_R keysyms. Alt Defined to be the modifier mapped to Alt_L and Alt_R. Mod1, M1 The first modifier. Mod2, M2, Alt The second modifier. Mod3, M3 Another modifier. Mod4, M4 Another modifier. Mod5, M5 Another modifier. Button1, B1 The first mouse button (left). Button2, B2 The second mouse button (middle). Button3, B3 The third mouse button (right). Button4, B4 The fourth mouse button. Button5, B5 The fifth mouse button. Double Matches double-press event. Triple Matches triple-press event. Quadruple Matches quadruple-press event. Any Matches any combination of modifiers. (Before Tk 4.0) The UNIX xmodmap program returns the current mappings from keys to these modifiers. The first column of its output lists the modifier. The rest of each line identifies the keysym(s) and low-level keycodes that are mapped to each modifier. The xmodmap program can also be used to change mappings. The following example shows the mappings on my system. Your setup may be different. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Example 29-2 Output from the UNIX xmodmap program xmodmap: up to 3 keys per modifier, (keycodes in parentheses): shift Shift_L (0x6a), Shift_R (0x75) lock Caps_Lock (0x7e) control Control_L (0x53) mod1 Meta_L (0x7f), Meta_R (0x81) mod2 Mode_switch (0x14) mod3 Num_Lock (0x69) mod4 Alt_L (0x1a) mod5 F13 (0x20), F18 (0x50), F20 (0x68) [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Event Sequences The bind command accepts a sequence of events in a specification, and most commonly this is a sequence of key events. In the following examples, the Key events are abbreviated to just the character detail, and soabc is a sequence of three Key events: bind . a {puts stdout A} bind . abc {puts stdout C} With these bindings in effect, both bindings are executed when the user types abc. The binding for a is executed when a is pressed, even though this event is also part of a longer sequence. This is similar to the behavior with Double and Triple event modifiers. For this reason you must be careful when binding sequences. You can use break in the binding for the prefix to ensure that it does not do anything: bindtags $w [list $w Text [winfo toplevel $w] all] bind $w <Control-x> break bind $w <Control-x><Control-s> {Save ; break} bind $w <Control-x><Control-c> {Quit ; break} The break ensures that the defaultText binding that inserts characters does not trigger. This trick is embodied byBindSequence in the next example. If a sequence is detected, then a break binding is added for the prefix. The procedure also supports theemacs convention that <Meta-x> is equivalent to <Escape>x. This convention arose because Meta is not that standard across keyboards. There is no meta key at all on Windows and Macintosh keyboards. The regexp command is used to pick out the detail from the<Meta> event. Example 29-3 Emacs-like binding convention for Meta and Escape proc BindSequence { w seq cmd } { bind $w $seq $cmd # Double-bind Meta-key and Escape-key if [regexp {<Meta-(.*)>} $seq match letter] { bind $w <Escape><$letter> $cmd } # Make leading keystroke harmless if [regexp {(<.+>)<.+>} $seq match prefix] { bind $w $prefix break } } The use of break and continue in bindings is not supported in Tk 3.6 and earlier. This is because only a single binding tag can match an event. To make a prefix of a sequence harmless in Tk 3.6, bind a space to it: bind $w $prefix { } This installs a binding for the widget, which suppresses the class binding in Tk 3.6. The space is different than a null string, {}. Binding to a null string deletes the current binding instead of replacing it with a harmless one. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Virtual Events A virtual event corresponds to one or more event sequences. When any of the event sequences occurs, then the virtual event occurs. Example 29-4 shows the cut, copy, and paste virtual events for each platform: Example 29-4 Virtual events for cut, copy, and paste switch $tcl_platform(platform) { "unix" { event add <<Cut>> <Control-Key-x> <Key-F20> event add <<Copy>> <Control-Key-c> <Key-F16> event add <<Paste>> <Control-Key-v> <Key-F18> } "windows" { event add <<Cut>> <Control-Key-x> <Shift-Key-Delete> event add <<Copy>> <Control-Key-c> <Control-Key-Insert> event add <<Paste>> <Control-Key-v> <Shift-Key-Insert> } "macintosh" { event add <<Cut>> <Control-Key-x> <Key-F2> event add <<Copy>> <Control-Key-c> <Key-F3> event add <<Paste>> <Control-Key-v> <Key-F4> } } You can define more than one physical event that maps to the same virtual event: event add <<Cancel>> <Control-c> <Escape> <Command-period> With this definition any of the physical events will trigger a <<Cancel>>. This would be convenient if the same user commonly used your application on different platforms. However, it is also possible that the physical bindings on different platforms overlap in conflicting ways. By default, virtual event definitions add to existing definitions for the same virtual event. The previous command could be replaced with these three: event add <<Cancel>> <Control-c> event add <<Cancel>> <Escape> event add <<Cancel>> <Command-period> Several widgets use virtual events as a notification mechanism. They generate virtual events in response to various conditions so that you can create bindings to respond to those conditions. For example, the listbox widget generates a <<ListboxSelect>> virtual event whenever the listbox selection changes. The easiest way to respond to changes to the listbox selection is to bind to this virtual event, for example: bind .lbox <<ListboxSelect>> {ListboxChanged %W} [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Generating Events Your application can use the event generate command to programmatically generate events, in essence emulating user interaction. You can generate either standard windowing events or virtual events. However, you can generate events only for the current application; you can't send events to other applications running on your system. (In other words, you can't use the event generate command to have your application control another application.) The first argument to event generate is the target widget for the event. You may provide either the path name of the widget, or the window identifier (such as returned by winfo id) as long as it is for a window in the current application. The second argument is an event specification, using the same syntax as for creating event bindings. (See "Event Syntax" on page 439.) However, you can't generate an event sequence (such as <KeyPress-Escape><KeyPress-a>), only single events. As an example, the following command delivers aButtonPress-3 event to a widget: event generate .b <ButtonPress-3> A widget must have focus to receive key events. Remember that a widget must have keyboard focus to receive KeyPress or KeyRelease events. You can use the focus command to assign keyboard focus to a widget: focus .e1 event generate .e1 <KeyPress-a> The event generate command also accepts options to specify additional attributes of the event, such as the x and y mouse position.Table 29-4 lists the event generate options. Of note is the-warp option, added in Tk 8.3. If you provide a-warp value of True, then the mouse pointer moves to the x and y coordinates of the generated event; otherwise, the mouse pointer remains at its current location. For example, the following commands moves the mouse pointer to the point 10,20 relative to the top-left corner of the main window: event generate . <Motion> -x 10 -y 20 -warp 1 [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Event Summary Event Command Syntax The event command is summarized in Table 29-3. Table 29-3. The event command event add virt phys1 phy2 ... Adds a mapping from one or more physical events to virtual eventvirt. event delete virt Deletes virtual event virt. event info Returns the defined virtual events. event info virt Returns the physical events that map tovirt. event generate win event ?opt val? ... Generates event for window win. The options are listed in Table 29-4. Event Keywords Table 29-4 lists the percent keywords and the corresponding option to the event generate command. Remember that keyword substitutions occur throughout the command, regardless of other Tcl quoting conventions. Keep your binding commands short, introducing procedures if needed. For the details about various event fields, consult the Xlib Reference Manual (O'Reilly & Associates, Inc.). The string values for the keyword substitutions are listed after a short description of the keyword. If no string values are listed, the keyword has an integer value like a coordinate or a window ID. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Thanks Table 29-4. A summary of the event keywords %% Use this to get a single percent sign. All events. %# -serial num The serial number for the event. All events. %a -above win The above field from the event. Configure event. %b -button num Button number. Events: ButtonPress and ButtonRelease. %c -count num The count field. Events: Expose and Map. %d -detail value The detail field. Values: NotifyAncestor, NotifyNonlinearVirtual, NotifyDetailNone, NotifyPointer, NotifyInferior, NotifyPointerRoot, NotifyNonlinear, or NotifyVirtual. Events: Enter, Leave, FocusIn, and FocusOut. %f -focus boolean The focus field (0 or 1). Events: Enter and Leave. %h -height num The height field. Events: Configure and Expose. %i The window field from the event, represented as a hexadecimal integer. All events. %k -keycode num The keycode field. Events: KeyPress and KeyRelease. %m -mode value The mode field. Values: NotifyNormal, NotifyGrab, NotifyUngrab, or NotifyWhileGrabbed. Events: Enter, Leave, FocusIn, and FocusOut. %o -override The override_redirect field. Events: Map, Reparent, and Configure. boolean %p -place value The place field. Values: PlaceOnTop, PlaceOnBottom. Circulate event. %s -state value The state field. A decimal string for events: ButtonPress, ButtonRelease, Enter, Leave, KeyPress, KeyRelease, and Motion. Values for the Visibility event: VisibilityUnobscured, VisibilityPartiallyObscured, or VisibilityFullyObscured. %t -time num %v The time field. All events. The value_mask field. Configure event. %w -width num The width field. Events: Configure and Expose. %x -x pixel The X coordinate, widget relative. Mouse events. %y -y pixel The Y coordinate, widget relative. Mouse events. %A The printing character from the event, or {}. Events: KeyPress and KeyRelease. %B -borderwidth The border width. Configure event. num %D -delta value The delta value. MouseWheel event. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks %E -sendevent The send_event field. All events. bool %K -keysym The keysym from the event. Events: KeyPress and KeyRelease. symbol %N The keysym as a decimal number. Events: KeyPress and KeyRelease. %P The atom name for the property being changed or deleted.Property event. %R -root win The root window ID. All events. %S -subwindow The subwindow ID. All events. win %T The type field. All events. %W The Tk pathname of the widget receiving the event. All events. %X -rootx pixel The x_root field. Relative to the (virtual) root window. Events: ButtonPress, ButtonRelease, KeyPress, KeyRelease, and Motion. %Y -rooty pixel The y_root field. Relative to the (virtual) root window. Events: ButtonPress, ButtonRelease, KeyPress, KeyRelease, and Motion. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Part IV: Tk Widgets Part IV describes the Tk widgets. These are the components you use to build up your graphical user interface. Tk widgets are simple to use, so you can rapidly develop your interface. At the same time, they have sophisticated features that you can use to fine-tune your interface in response to user feedback. Chapter 30 describes buttons and menus. Tk 8.0 adds native look and feel to these widgets, so a single script will look different depending on the platform it is running on. Associated with the widgets is a resource database that stores settings like colors and fonts. Chapter 31 describes the resource database and generalizes it to store button and menu configurations. Chapter 32 describes a few simple widgets. The frame, labelframe, and toplevel are containers for other widgets. The label displays a text string. The message formats a long text string onto multiple lines. The scale represents a numeric value. The bell command rings the terminal bell. Chapter 33 describes scrollbars, which can be attached in a general way to other widgets. Chapter 34 describes entry widgets, which provide one line of editable text and spinboxes, which allow users to select from multiple values by "spinning" through selections. Chapter 35 describes the listbox widget that displays several lines of text. The lines are manipulated as units. Chapter 36 describes the general-purpose text widget. It can display multiple fonts and have binding tags on ranges of text. Chapter 37 describes the canvas widget. The canvas manages objects like lines, boxes, images, arcs, and text labels. You can have binding tags on these objects and classes of objects. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Chapter 30. Buttons and Menus Buttons and menus are the primary way that applications expose functions to users. This chapter describes how to create and manipulate buttons and menus. A button widget is associated with a Tcl command that invokes an action in the application. The checkbutton and radiobutton widgets affect an application indirectly by controlling a Tcl variable. A menu elaborates on this concept by organizing button-like items into related sets, including cascaded menus. The menubutton widget is a special kind of button that displays a menu when you click on it. Tk 8.0 provides a cross-platform menu bar facility. The menu bar is really just a menu that is displayed horizontally along the top of your application's main window. On the Macintosh, the menu bar appears at the top of the screen. You define the menu bar the same on all platforms. Tk 8.0 also uses native button and menu widgets on the Windows and Macintosh platforms. This contributes to a native look and feel for your application. In earlier versions, Tk displayed the widgets identically on all platforms. Associating a command to a button is usually quite simple, as illustrated by the Tk "Hello, World!" example: button .hello -command {puts stdout "Hello, World!"} This chapter describes a few useful techniques for setting up the commands in more general cases. If you use variables inside button commands, you have to understand the scoping rules that apply. This is the first topic of the chapter. Once you get scoping figured out, then the other aspects of buttons and menus are quite straightforward. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Button Commands and Scope Issues Perhaps the trickiest issue with button commands has to do with variable scoping. A button command is executed at the global scope, which is outside of any procedure. If you create a button while inside a procedure, then the button command executes in a different scope later. The commands used in event bindings also execute later at the global scope. I think of this as the "now" (i.e., button definition) and "later" (i.e., button use) scope problem. For example, you may want to use the values of some variables when you define a button command but use the value of other variables when the button command is used. When these two contexts are mixed, it can be confusing. The next example illustrates the problem. The button's command involves two variables: x and val. The global variable x is needed later, when the button's command executes. The local variableval is needed now, in order to define the command. Example 30-1 shows this awkward mixture of scopes: Example 30-1 A troublesome button command proc Trouble {args} { set b 0 # Display the value of x, a global variable label .label -textvariable x set f [frame .buttons -borderwidth 10] # Create buttons that multiply x by their value foreach val $args { button $f.$b -text $val \ -command "set x \[expr \$x * $val\]" pack $f.$b -side left incr b } pack .label $f } set x 1 Trouble -1 4 7 36 The example uses a label widget to display the current value of x. The textvariable attribute is used so that the label displays the current value of the variable, which is always a global variable. It is not necessary to have a global command inside Trouble because the value of x is not used there. The button's command is executed later at the global scope. The definition of the button's command is ugly, though. The value of the loop variable val is needed when the button is defined, but the rest of This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks the substitutions need to be deferred until later. The variable substitution of $x and the command substitution ofexpr are suppressed by quoting with backslashes: set x \[expr \$x * $val\] In contrast, the following command assigns a constant expression to x each time the button is clicked, and it depends on the current value of x, which is not defined the first time through the loop. Clearly, this is incorrect: button $f.$b -text $val \ -command "set x [expr $x * $val]" Another incorrect approach is to quote the whole command with braces. This defers too much, preventing the value of val from being used at the correct time. Use procedures for button commands. The general technique for dealing with these sorts of scoping problems is to introduce Tcl procedures for use as the button commands. Example 30-2 introduces a little procedure to encapsulate the expression: Example 30-2 Fixing the troublesome situation proc LessTrouble { args } { set b 0 label .label -textvariable x set f [frame .buttons -borderwidth 10] foreach val $args { button $f.$b -text $val \ -command "UpdateX $val" pack $f.$b -side left incr b } pack .label $f } proc UpdateX { val } { global x set x [expr $x * $val] } set x 1 LessTrouble -1 4 7 36 It may seem just like extra work to introduce the helper procedure, UpdateX. However, it makes the code clearer in two ways. First, you do not have to struggle with backslashes to get the button command defined correctly. Second, the code is much clearer about the function of the button. Its job is to update the global variable x. You can generalize UpdateX to work on any variable by passing the name of the variable to update. Now it becomes much like theincr command: This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks button $f.$b -text $val -command "Update x $val" The definition of Update uses upvar, which is explained on page 91, to manipulate the named variable in the global scope: proc Update {varname val} { upvar #0 $varname x set x [expr $x * $val] } Double quotes are used in the button command to allow $val to be substituted. Whenever you use quotes like this, you have to be aware of the possible values for the substitutions. If you are not careful, the command you create may not be parsed correctly. The safest way to generate the command is with list: button $f.$b -text $val -command [list UpdateX $val] Using list ensures that the command is a list of two elements,UpdateX and the value of val. This is important because UpdateX takes only a single argument. If val contained white space, then the resulting command would be parsed into more words than you expected. Of course, in this case we plan to always call LessTrouble with an integer value, which does not contain white space. Example 30-3 provides a more straightforward application of procedures for button commands. In this case the advantage of the procedure MaxLineLength is that it creates a scope for the local variables used during the button action. This ensures that the local variables do not accidentally conflict with global variables used elsewhere in the program. There is also the standard advantage of a procedure, which is that you may find another use for the action in another part of your program. Example 30-3 A button associated with a Tcl procedure proc MaxLineLength { file } { set max 0 if [catch {open $file} in] { return $in } foreach line [split [read $in] \n] { set len [string length $line] if {$len > $max} { set max $len } } return "Longest line is $max characters" } # Create an entry to accept the file name, # a label to display the result # and a button to invoke the action This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks . config -borderwidth 10 entry .e -width 30 -bg white -relief sunken button .doit -text "Max Line Length" \ -command {.label config -text [MaxLineLength [.e get]]} label .label -text "Enter file name" pack .e .doit .label -side top -pady 5 The example is centered around the MaxLineLength procedure. This opens a file and loops over the lines finding the longest one. The file open is protected with catch in case the user enters a bogus file name. In that case, the procedure returns the error message from open. Otherwise, the procedure returns a message about the longest line in the file. The local variables in, max, and len are hidden inside the scope of the procedure. The user interface has three widgets: an entry for user input, the button, and a label to display the result. These are packed into a vertical stack, and the main window is given a border. Obviously, this simple interface can be improved in several ways. There is no Quit button, for example. All the action happens in the button command: .label config -text [MaxLineLength [.e get]] Braces are used when defining the button command so that the command substitutions all happen when the button is clicked. The value of the entry widget is obtained with .e get. This value is passed into MaxLineLength, and the result is configured as the text for the label. This command is still a little complex for a button command. For example, suppose you wanted to invoke the same command when the user pressed <Return> in the entry. You would end up repeating this command in the entry binding. It might be better to introduce a one-line procedure to capture this action so that it is easy to bind the action to more than one user action. Here is how that might look: proc Doit {} { .label config -text [MaxLineLength [.e get]] } button .doit -text "Max Line Length" -command Doit bind .e <Return> Doit Chapter 29 describes the bind command in detail, Chapter 32 describes the label widget, andChapter 35 describes the entry widget. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Buttons Associated with Tcl Variables The checkbutton and radiobutton widgets are associated with a global Tcl variable. When one of these buttons is clicked, a value is assigned to the Tcl variable. In addition, if the variable is assigned a value elsewhere in the program, the appearance of the checkbutton or radiobutton is updated to reflect the new value. A set of radiobuttons all share the same global variable. The set represents a choice among mutually exclusive options. In contrast, each checkbutton has its own global variable. The ShowChoices example uses a set of radiobuttons to display a set of mutually exclusive choices in a user interface. TheShowBooleans example uses checkbutton widgets: Example 30-4 Radiobuttons and checkbuttons proc ShowChoices { parent varname args } { set f [frame $parent.choices -borderwidth 5] set b 0 foreach item $args { radiobutton $f.$b -variable $varname \ -text $item -value $item pack $f.$b -side left incr b } pack $f -side top } proc ShowBooleans { parent args } { set f [frame $parent.booleans -borderwidth 5] set b 0 foreach item $args { checkbutton $f.$b -text $item -variable $item pack $f.$b -side left incr b } pack $f -side top } set choice kiwi ShowChoices {} choice apple orange peach kiwi strawberry set Bold 1 ; set Italic 1 . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks ShowBooleans {} Bold Italic Underline The ShowChoices procedure takes as arguments the parent frame, the name of a variable, and a set of possible values for that variable. If the parent frame is null, {}, then the interface is packed into the main window.ShowChoices creates a radiobutton for each value, and it puts the value into the text of the button. It also has to specify the value to assign to the variable when the button is clicked because the default value associated with a radiobutton is the empty string. The ShowBooleans procedure is similar to ShowChoices. It takes a set of variable names as arguments, and it creates a checkbutton for each variable. The default values for the variable associated with a checkbutton are zero and one, which is fine for this example. If you need particular values, you can specify them with the -onvalue and -offvalue options. Radiobuttons and checkbuttons can have commands associated with them, just like ordinary buttons. The command is invoked after the associated Tcl variable has been updated. Remember that the Tcl variable associated with the button is defined in the global scope. For example, you could log the changes to variables as shown in the next example. Example 30-5 A command on a radiobutton or checkbutton proc PrintByName { varname } { upvar #0 $varname var puts stdout "$varname = $var" } checkbutton $f.$b -text $item -variable $item \ -command [list PrintByName $item] radiobutton $f.$b -variable $varname \ -text $item -value $item \ -command [list PrintByName $varname] [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Button Attributes Table 30-1 lists the attributes for the button, checkbutton, menubutton, and radiobutton widgets. Unless otherwise indicated, the attributes apply to all of these widget types. Chapters 40, 41, and 42 discuss many of these attributes in more detail. Some attributes are ignored on the Windows and Macintosh platforms because they are not supported by the native button widgets. The table uses the resource name for the attributes, which has capitals at internal word boundaries. In Tcl commands, the attributes are specified with a dash and they are all lowercase. Compare: option add *Menubutton.activeBackground: red .mb configure -activebackground red The first command defines a resource database entry that covers all menubuttons and gives them a red active background. This only affects menubuttons created after the database entry is added. The second command changes an existing menubutton (.mb) to have a red active background. Note the difference in capitalization of background in the two commands. Theresource database is introduced on page 372, and Chapter 31 explains how to use the resource database in more detail. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 30-1. Resource names of attributes for all button widgets activeBackground Background color when the mouse is over the button. activeForeground Text color when the mouse is over the button. anchor Anchor point for positioning the text. background The normal background color. bitmap A bitmap to display instead of text. borderWidth Width of the border around the button. command Tcl command to invoke when button is clicked. compound Where the image or bitmap should be placed relative to the text: bottom, center, left, right, top or none (default). (Tk 8.4) cursor Cursor to display when mouse is over the widget. default active displays as a default button.normal and disabled display as normal button. See page 809 (Tk 8.0). direction up, down, left, right, active. Offset direction for posting menus.menubutton. (Tk 8.0). disabledForeground Foreground (text) color when button is disabled. font Font for the text. foreground Foreground (text) color. (Alsofg). height Height, in lines for text, or screen units for images. highlightBackground Focus highlight color when widget does not have focus. highlightColor Focus highlight color when widget has focus. highlightThickness Width of highlight border. image Image to display instead of text or bitmap. indicatorOn Boolean that controls if the indicator is displayed. checkbutton, menubutton, and radiobutton. justify Text justification: center, left, or right. menu Menu posted when menubutton is clicked. offRelief Alternate relief style when the widget is deselected.checkbutton and radiobutton. (Tk 8.4) offValue Value for Tcl variable when checkbutton is not selected. onValue Value for Tcl variable when checkbutton is selected. overRelief Alternate relief style when mouse is over the widget.button, checkbutton, and radiobutton. (Tk 8.4) padX Extra space to the left and right of the button text. padY Extra space above and below the button text. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks relief flat, sunken, raised, groove, solid or ridge. repeatDelay The number of milliseconds a button or key must be held down before it begins to auto-repeat. For button only. (Tk 8.4) repeatInterval The number of milliseconds between auto-repeats. Forbutton only. (Tk 8.4) selectColor Color for selector. checkbutton or radiobutton. selectImage Alternate graphic image for selector: checkbutton or radiobutton. state normal (enabled), disabled (deactivated), or active (when the mouse pointer is over the button). takeFocus Control focus changes from keyboard traversal. text Text to display in the button. textVariable Tcl variable that has the value of the text. underline Index of text character to underline. value Value for Tcl variable when radiobutton is selected. variable Tcl variable associated with the button: checkbutton or radiobutton. width Width in characters for text, or screen units for image. As of Tk 8.4, on Windows only, a negative value is treated as a minimum width for button widgets only. wrapLength Maximum character length before text is wrapped, in screen units. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Button Operations Table 30-2 summarizes the operations on button widgets. In the table,$w is a button, checkbutton, radiobutton, or menubutton, except when noted. For the most part, these operations are used by the script libraries that implement the bindings for buttons. The cget and configure operations are the most commonly used by applications. Table 30-2. Button operations $w cget option Returns the value of the specified attribute. $w configure ?option? ?value? ... Queries or manipulates the configuration information for the widget. $w deselect Deselects the radiobutton or checkbutton. Set the radiobutton variable to the null string. Set the checkbutton variable to the off value. $w flash Redisplays the button several times in alternate colors. $w invoke Invokes the command associated with the button. $w select Selects the radiobutton or checkbutton, setting the associated variable appropriately. $w toggle Toggles the state of the checkbutton, setting the associated variable appropriately. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Menus and Menubuttons A menu presents a set of button-like menu entries to users. A menu entry is not a full fledged Tk widget. Instead, you create a menu widget and then add entries to the menu as shown in the following examples. There are several kinds of menu entries: Command entries are like buttons. Check entries are like checkbuttons. Radio entries are like radiobuttons. Separator entries are used to visually set apart entries. Cascade entries are used to post submenus. Tear-off entries are used to detach a menu from its menu button so that it becomes a new top-level window. A menubutton is a special kind of button that posts (i.e., displays) a menu when you press it. If you click on a menubutton, then the menu is posted and remains posted until you click on a menu entry to select it, or click outside the menu to dismiss it. If you press and hold the menubutton, then the menu is unposted when you release the mouse. If you release the mouse over the menu, it selects the menu entry that was under the mouse. You can have a command associated with a menubutton, too. The command is invoked before the menu is posted, which means you can compute the menu contents when the user presses the menubutton. Our first menu example creates a sampler of the different entry types: Example 30-6 A menu sampler This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks menubutton .mb -text Sampler -menu .mb.menu pack .mb -padx 10 -pady 10 set m [menu .mb.menu -tearoff 1] $m add command -label Hello! -command {puts "Hello, World!"} $m add check -label Boolean -variable foo \ -command {puts "foo = $foo"} $m add separator $m add cascade -label Fruit -menu $m.sub1 set m2 [menu $m.sub1 -tearoff 0] $m2 add radio -label apple -variable fruit -value apple $m2 add radio -label orange -variable fruit -value orange $m2 add radio -label kiwi -variable fruit -value kiwi The example creates a menubutton and two menus. The main menu .mb.menu is a child of the menubutton.mb. This relationship is necessary so that the menu displays correctly when the menubutton is selected. Similarly, the cascaded submenu .mb.menu.sub1 is a child of the main menu. The first menu entry is represented by the dashed line. This is a tear-off entry that, when selected, makes a copy of the menu in a new top-level window. This is useful if the menu operations are invoked frequently. The -tearoff 0 argument is used when creating the submenu to eliminate its tear-off entry. The command, radio, and check entries are similar to the corresponding button types. The configuration options for menu entries are similar to those for buttons. The main difference is that the text string in the menu entry is defined with the -label option, not -text. Table 30-6 gives the complete set of options for menu entries. The cascade menu entry is associated with another menu. It is distinguished by the small right arrow in the entry. When you select the entry, the submenu is posted. It is possible to have several levels of cascaded menus. There is no limit to the number of levels, except that your users will complain if you nest too many menus. A Menu Bar You can create a menu bar manually by packing several menubuttons into a frame. The default bindings on menubuttons are such that you can drag your mouse over the menu bar and the different menus will display as you drag over their menubutton. Tk 8.0 lets you create a menu bar as a horizontal menu that is associated with a top-level window. On Windows and UNIX the menu is displayed along the top of the window. On Macintosh this menu replaces the main menu along the top of the screen when the window is activated. The menu bar menu should have all cascade entries so that when you select an entry, another menu is displayed. This is illustrated in Example 30-7. It defines variables that store the names of the menu widgets: set $m [menu .menubar.m$m] This creates a variable named File, Edit, and Help that store the names of the menu widgets. This trick is generalized on page 470 in a package that hides the menu widget names. Example 30-7 A menu bar in Tk 8.0 menu .menubar # attach it to the main window . config -menu .menubar . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks # Create more cascade menus foreach m {File Edit Help} { set $m [menu .menubar.m$m] .menubar add cascade -label $m -menu .menubar.m$m } $File add command -label Quit -command exit # add more menu items... System Menus The Tk 8.0 menu bar implementation can add entries to the Windows system menu, the Macintosh Apple menu, and the Help menu on all platforms. This works by recognizing special names. For example, if the menu bar is .menubar, then the special names are.menubar.system, .menubar.apple, and .menubar.help. The Help menu is right justified on all platforms. The Apple menu is normally used by applications for their About... entry. The entries you add to the Apple menu are added to the top of the menu. The System menu appears in the Windows title bar and has entries such as Close and Minimize. Pop-Up Menus A pop-up menu is not associated with a menubutton. Instead, it is posted in response to a keystroke or other event in the application. The tk_popup command posts a pop-up menu: tk_popup menu x y ?entry? The last argument specifies the entry to activate when the menu is posted. It is an optional parameter that defaults to 1, which avoids the tear-off entry in position zero. The menu is posted at the specified X and Y coordinates in its parent widget. Option Menus An option menu represents a choice with a set of radio entries, and it displays the current choice in the text of the menubutton. The tk_optionMenu command creates a menubutton and a menu full of radio entries: tk_optionMenu w varname firstValue ?value value ...? The first argument is the pathname of the menubutton to create. The second is the variable name. The third is the initial value for the variable, and the rest are the other choices for the value. The menubutton displays the current choice and a small symbol, the indicator, to indicate it is an option menu. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register . it. Than Multicolumn Palette Menus Tk 8.0 adds a -columnbreak menu entry attribute that puts the entry at the top of a new column. This is most useful when the menu consists of several images that are arranged as a palette. Set the entry's image with the -image attribute. You can create checkbutton and radiobutton entries that have images and no indicator by using the -hidemargin attribute. In this case, a selected entry is indicated by drawing a solid rectangle around it. [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com . to register it. Thanks [ Team LiB ] Menu Bindings and Events Keyboard Traversal The default bindings for menus allow for keyboard selection of menu entries. The selection process is started by pressing <Alt-x>, where x is the distinguishing letter for a menubutton or a menu bar's cascade entry. The underline attribute is used to highlight the appropriate letter. The underline value is a number that specifies a character position, and the count starts at zero. For example, File a menu with a highlighted F is created for a menubutton like this: menubutton .menubar.file -text File -underline 0 \ -menu .menubar.file.m If the File menu is implemented as a menu bar cascade, you create the traversal highlight like this: menu .mbar . configure -menu .mbar .mbar add cascade -label File -underline 0 \ -menu .mbar.file When the user types <Alt-f> over the main window, the menu is posted. The case of the highlighted letter is not important. After a menu is posted, the arrow keys change the selected entry. The <Up> and <Down> keys move within a menu, and the<Left> and <Right> keys move between adjacent menus. The bindings assume that you create your menus from left to right. If any of the menu entries have a letter highlighted with the -underline option, typing that letter invokes that menu entry. For example, anExport entry that is invoked by typing x can be created like this: .menubar.file.m add command -label Export -underline 1 \ -command File_Export The <space> and <Return> keys invoke the menu entry that is currently selected. The<Escape> key aborts the menu selection and removes the menu. Menu Virtual Events As of Tk 8.0, a menu widget generates a <<MenuSelect>> virtual event whenever the menu's active entry changes. The event is fired after the menu selection has changed, so the binding action can access the new selection. The easiest way to be aware of changes to the menu selection is to bind to this virtual event, as shown in Example 30-8. Notification like this is useful for features such as context-sensitive help. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks. Example 30-8 Using the <<MenuSelect>> virtual event proc MenuChanged {w} { puts "Menu $w selection: [$w entrycget active -label]" } bind .mbar.file <<MenuSelect>> {MenuChanged %W} [ Team LiB ] This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] Manipulating Menus and Menu Entries There are a number of operations that apply to menu entries. We have already introduced the add operation. The entryconfigure operation is similar to the configure operation for widgets. It accepts the same attribute-value pairs used when the menu entry was added. The delete operation removes a range of menu entries. The rest of the operations are used by the library scripts that implement the standard bindings for menus. A menu entry is referred to by an index. The index can be numerical, counting from zero, or symbolic.Table 30-3 summarizes the index formats. One of the most useful indices is a pattern that matches thelabel in the menu entry. The pattern matching is done with the rules of string match. Using a pattern eliminates the need to keep track of the numerical indices. Table 30-3. Menu entry index keywords index A numerical index counting from zero. active The activated entry, either because it is under the mouse or has been activated by keyboard traversal. end The last menu entry. last The same as end. none No entry at all. @ycoord The entry under the given Y coordinate. Use@%y in bindings. pattern A string match pattern to match the label of a menu entry. Table 30-4 summarizes the complete set of menu operations. In the table,$w is a menu widget. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Table 30-4. Menu operations $w activate index Highlights the specified entry. $w add type ?option value? ... Adds a new menu entry of the specified type with the given values for various attributes. $w cget option Returns the value for the configuration option. $w clone Makes a linked copy of the menu. This is used to implement tear-offs and menu bars. $w configure ?option? ?value? ... Returns the configuration information for the menu. $w delete i1 ?i2? Deletes the menu entries from index i1 to i2. $w entrycget index option Returns the value of option for the specified entry. $w entryconfigure index ?option? ?value? ... Queries or modifies the configuration information for the specified menu entry. $w index index Returns the numerical value of index. $w insert type index ?option value? ... Like add, but inserts the new entry after the specifiedindex. $w invoke index Invokes the command associated with the entry. $w post x y Displays the menu at the specified coordinates. $w postcascade index Displays the cascade menu from entry index. $w type index Returns the type of the entry at index. $w unpost Unmaps the menu. $w yposition index Returns the Y coordinate of the top of the entry. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks Menu Attributes A menu has a few global attributes, and then each menu entry has many button-like attributes that describe its appearance and behavior. Table 30-5 specifies the attributes that apply globally to the menu, unless overridden by a per-entry attribute. The table uses the X resource names, which may have a capital at interior word boundaries. In Tcl commands, use all lowercase and a leading dash. Table 30-5. Menu attribute resource names activeBackground Background color when the mouse is over a menu entry. activeBorderWidth Width of the raised border around active entries. activeForeground Text color when the mouse is over a menu entry. background The normal background color for menu entries. borderWidth Width of the border around the menu (except on systems where native menus are used, such as Windows). cursor Cursor to display when mouse is over the menu. disabledForeground Foreground (text) color when menu entries are disabled. font Default font for the text. foreground Foreground color. (Also fg). postCommand Tcl command to run just before the menu is posted. relief The relief style of the menu (except on systems where native menus are used such, as Windows). selectColor Color for selector in check and radio type entries. takeFocus Control focus changes from keyboard traversal. tearOff True if menu should contain a tear-off entry. tearOffCommand Command to execute when menu is torn off. Two arguments are added: the original menu and the new tear-off. title Title for the window created when the menu is torn off. If this is an empty string (default), the title is the text of the menubutton or cascade item from which this menu was torn off. (Tk 8.0) type (Read-only) normal, menubar, or tearoff. (Tk 8.0). Table 30-6 describes the attributes for menu entries, as you would use them in a Tcl command (i.e., all lowercase with a leading dash.) The attributes for menu entries are not supported directly by the resource database. However, Example 31-6 on page 481 describes how you can use the resource database for menu entries. Table 30-6. Attributes for menu entries -activebackground Background color when the mouse is over the entry. This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks -activeforeground Foreground (text) color with mouse is over the entry. -accelerator Text to display as a reminder about keystroke binding. -background The normal background color. -bitmap A bitmap to display instead of text. -columnbreak Puts the entry at the start of a new column. (Tk 8.0). -command Tcl command to invoke when entry is invoked. -compound Where the image or bitmap should be placed relative to the text:bottom, center, left, right, top or none (default). (Tk 8.4) -font Default font for the text. -foreground Foreground color. (Also fg). -hidemargin Suppresses the margin reserved for button indicators. (Tk 8.0). -image Image to display instead of text or bitmap. -indicatoron Boolean that controls if the indicator is displayed:check and radio entries. -label Text to display in the menu entry. -menu Menu posted when cascade entry is invoked. -offvalue Variable value when check entry is not selected. -onvalue Value for Tcl variable when check entry is selected. -selectcolor Color for selector: check and radio entries. -selectimage Alternate image to use when entry is selected: check and radio entries. -state The state: normal, active, or disabled -underline Index of text character to underline. -value Value for Tcl variable when radiobutton entry is selected. -variable Tcl variable associated with the check or radio entry. [ Team LiB ] . This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks [ Team LiB ] A Menu by Name Package If your applicati