The xskill program is written in Python using the Tkinter GUI (graphical user interface) widget set. Refer to the Python help page for general information about Python and Tkinter.
The xskill application is written in Python using Cleanroom intended functions. For more about Cleanroom, read Dr. Allan M. Stavely's book Toward Zero-Defect Programming, or see John Shipman's cleanroom pages.
The applications has these principal steps:
The command line arguments are processed by instantiating an Args object. Command line options include:
-p peoplefile: For testing purposes, you can use this option to specify an alternate people file.
-m matrixfile: For testing purposes, you can use this option to specify an alternatematrix file.
If a positional argument is given, it must be the login name of an employee in the people file, and the user must be in the tccadmin net-group.
We attempt to check out the knowledge matrix file from RCS, with a lock, using the rcslib.py module. If this fails, the program terminates.
The people file and the knowledge matrix database are read by instantiating the People object and the Skills object, respectively.
If there are errors in either of these files, the program terminates.
The global variable forPerson is set to a Person object that represents the person whose skills are being updated.
The SkillsApp object is instantiated; this object contains all the Tkinter code. Then its .mainloop() method is invoked to actually run the X application.
If the X application raises SystemExit, the matrix file is checked in to RCS without changes, which will release the lock.
If the X application terminates normally, the matrix file is rewritten in place and then checked back in to RCS.
The Args object is located in source file xskillobjs.py. This object represents the command line arguments passed to the program, and its constructor checks these arguments for validity.
Exports from this object include:
Args(): The constructor. Gets its input from sys.argv. Uses the standard Python getopt module for gross argument checking.
.peopleFileName: The effective people file name: None if no -p option is specified, otherwise the -p option.
.matrixFileName: The effective matrix file name: the -m option, or DEFAULT_MATRIX_FILE if there is no -m option.
.byLogin: The login name of the user running the program.
.forLogin: The login name of the user whose skills are to be updated.
This object holds the actual Tkinter part of the application. Its exports include:
SkillsApp(skillsDB, forPerson): The constructor. The skillsDB argument must be a Skills object), and the forPerson argument must be a Person object), representing the person whose skills are to be updated.
.mainLoop(): Calling this method actually runs the application.
Widgets in the root window of this application include:
A label displaying the program name and the name of the person whose skills are being updated.
A PageTurner widget containing ``Next page'' and ``Prev page'' buttons and a frame that displays each page of the survey in turn. See the PageTurner object.
A Quit button at the bottom.
The work of extracting the skills and formatting them as a series of pages is done by a SkillsPagination object, which contains a list of Frame widgets representing the pages. See the SkillsPagination object.
Important note: All widget placements within it should use the .grid geometry manager, not the Packer or Placer. Failure to observe this requirement will cause your application to hang with no messages.
This object represents all the headings and skills from a Skills object, only they are broken into page-sized chunks represented by a list of SkillsPage objects.
Exports include:
SkillsPagination(master): Constructor. The argument must be the .bodyFrame member of a PageTurner widget (see Section 7.6). Returns an empty SkillsPagination object.
.addSkillsGroup(skillsGroup, forPerson): This method adds part or all of a skills tree to itself. The skillsGroup argument must be a SkillsGroup object---it may be the root of an entire skills tree, or any subtree. The forPerson argument must be a Person object from the people.py module, describing which person's skills should be added.
.pageList: The list of SkillsPage objects containing all the skills added to self so far.
This object represents one page's worth of skill radiobuttons and their associated headings. Exports:
SkillsPage(master): Constructor. The argument must be the .bodyFrame member of a PageTurner widget (see Section 7.6). Returns an empty SkillsPage object.
.frame: The Frame widget in which the Tkinter widgets for this page are placed.
.addWidget(w): Adds a widget to self. The argument w must be an unplaced widget slaved to self.frame.
.heightLeft(): Returns the number of pixels of height still available in self.
.nChildren(): Returns the number of widgets in self. This method is necessary so that the SkillsPagination object can tell whether a page is empty or not.
The purpose of this object is to encapsulate all information about the size and appearance of widgets to be placed in a SkillsPage object.
This object uses the Singleton pattern (see Gamma et al., Design Patterns); that is, the first caller will get a new instance, but all successive callers will get the same instance. The NodeFactory() function is a wrapper for the _NodeFactory object that implements Singleton.
Exports include:
NodeFactory(): Constructor. Returns the singleton instance.
.instance: The singleton instance.
.nodeHeight(depth): If depth is equal to skills.LEAF_DEPTH, returns the height in pixels of the widget that will be made for a skill. Otherwise it returns the height in pixels of the widget that will be created for a skill heading of the given depth. Depths are defined in the skills.py module.
.makeHeading(master, skillsGroup, suffix=""): Manufactures a heading widget. Arguments:
master: The Tkinter Frame widget to which the new widget is to be slaved.
skillsGroup: A SkillsGroup widget representing the heading to be manufactured.
suffix: Optional. If omitted, a normal heading will be made. If a string is supplied, that string is appended to the heading text. The purpose of this feature is to allow the caller to append the string " (continued)" to continuation headings.
.makeLeaf(master, skillDetail, forPerson): Manufactures the group of radiobuttons and description for one skill. Arguments:
master: The Tkinter Frame widget to which the new widget is to be slaved.
skillDetail: The SkillDetail object describing the specific skill for which we are making a radiobutton group.
forPerson: A Person object describing whose skills are to be displayed.
This object is a Tkinter widget designed to present a series of frames as if they were pages of a book, along with `Next page' and `Prev page' buttons to allow the user to page forward and backward through the list. This object is basically a container class for Frame widgets, referred to as child frames. It was written for the xskill application, but may be useful in its own right.
To use this widget:
Instantiate it in some parent window. Let's call this instance PT.
If you would like to place additional widgets between the Next page/Prev page widgets and the page body, you can create them mastered to PT.headFrame and place them using .grid().
Create each child frame f as a Frame widget mastered to PT.bodyFrame, then add it using the PT.addPage(f) method. Do not grid these widgets; they will be gridded later.
Use the PT.setPageNo(n) method to display the nth page (counting from 0). If you don't call this method, the first page will be displayed as soon as it is added.
Exports include:
PageTurner(master): Constructor. The argument specifies the parent frame for self, and must be a Tkinter Frame widget.
.headFrame: A Frame widget that you can use to place additional widgets between the Next page/Prev page buttons and the page body, if you so desire.
.bodyFrame: The Frame widget to which all child frames must be slaved.
.addPage(f): Adds a child frame to self. Argument f must be a Frame widget slaved to self.bodyFrame.
.setPageNo(n): Sets the page number. The argument n must be a nonnegative number smaller than the number of child pages in self (that is, smaller than the length of self.pageList, or you will get an IndexError exception.
There is one important trick embodied in this object. The child frames are mastered to self.bodyFrame, but they are not gridded. The .grid() method is used to make a page appear, and the .grid_forget method is used to erase it.
This module represents the ``people file'' defining all current TCC employees. It contains three objects:
The People object represents the entire file. Since the file is in XML format, the module uses Python's xml.dom.minidom module to access the XML elements.
This module's exports include:
People(fileName=None): Constructor. If no argument is given, defaults to the ``official'' people file, but you can supply the name of an alternative file in the same format. See People file format for the format of this file.
.depts: A list of Dept objects representing the departments in self (see below).
.loginMap: A dictionary containing all the people from the file. The keys are the login names, and each value is a People object (see below).
The Dept object represents one department (or grouping of people). Exports:
Dept(name): Constructor. The argument is a string giving the department's name.
.name: The department's name as a string.
.personList: A list of Person objects representing the people in this department.
.append(person): Appends a person to the department. The argument must be a Person object.
The Person object represents one employee. Exports:
Person(dept, initials, login, last, first, url): Constructor; arguments as in the members listed below.
.dept: A Dept object representing the person's department.
.initials: The person's initials as a string. These initials must be unique.
.login: The person's account name.
.last: The person's last name.
.first: The person's first name and optional nickname.
.url: If the person has a homepage, this is their homepage's URL. Otherwise, it is None.
The str() function is defined on objects of this type, and returns the object formatted as a string.
This module contains a number of objects to represent the ``skills matrix;'' see Matrix file format for the format of this file.
Since the file is in XML format, the module uses Python's xml.dom.minidom module to access the XML elements.
Contents include:
.LEAF_DEPTH: An integer defining the depth of a specific skill in the tree. At this writing, this value is 3: level 0 is the root, level 1 is for major skill areas, level 2 is for skill groups, and level 3 is for individual skills.
The SkillsDatabase object represents the entire file. Exports include:
SkillsDatabase(people, matrixFilename=None): Constructor. The first argument must be a People object (see The xml_people.py module). The second argument is optional and names the skill matrix file; the default is the official skill matrix file.
.people: The first argument to the constructor.
.root: The SkillsGroup object representing the entire tree of skills.
.matrixFileName: The name of the matrix file, whether passed in or defaulted.
The str() function is defined on this object, and returns as a result one string containing the entire skills database in matrix file format.
A SkillsGroup object represents the entire tree of skills, or any subtree. Exports include:
SkillsGroup(people, parent, depth, scan): Constructor. Reads the next group at this level, and all its children, from the given scan object. Arguments include:
people: The People object used to look up login names in the matrix file.
parent: For the root object, pass None; for subtrees, pass the parent SkillsGroup.
depth: Depth of the object; 0 for the root; must be less than skills.LEAF_DEPTH.
scan: A Scan object representing the matrix file; see Section 7.10.
.title: The title of this skill grouping, as a string.
.depth: As passed to the constructor.
.parent(): For the root object, returns None, else returns the parent SkillsGroup object.
.nChildren(): Returns the number of children of self.
.nthChild(n): Returns the (n)th child of self, or raises IndexError if the child number is out of range.
.tree: The Tree object related to this node. See Section 7.10.
The str() function is defined for objects of this type, and returns a string representing self formatted as in the matrix file.
A SkillDetail object represents one particular skill and the people who have that skill. Exports include:
SkillDetail(people, parentTree, scan): Constructor; returns a new, empty object. Arguments:
people: The People object used to look up login names.
parentTree: The Tree object from the parent SkillsGroup object.
scan: The Scan object used to scan the matrix file. See Section 7.10.
.title: The name of the skill, as a string.
.holderMap: A dictionary containing the PersonSkill objects for this skill. For each login name L, .holderMap[L] is the PersonSkill object describing the skill level for the person with that login name.
.parent(): Returns the parent SkillsGroup object containing self.
.getRating(person): Returns a Rating object describing the skill live for the person represented by the given Person object.
.setRating(person, rating): Changes the skill level for person (a Person object) to rating (a Rating object).
The str() function is defined for objects of this type, and returns a string representing self formatted as in the matrix file.
A PersonSkill object represents one skill rating for one person. Exports include:
PersonSkill(person, rating): Constructor. The first argument is a Person object representing the skill-holder, and the second argument is a Rating object describing their level of skill.
.person: As passed to the constructor.
.rating: As passed to the constructor.
The str() function is defined for these objects, and returns a string representing self, formatted as in the matrix file.
The RatingSystem object is a Singleton object that represents a system for describing different skill levels. Exports:
RatingSystem(): Constructor; returns the singleton instance of self.
.nLevels: Number of skill levels, as an integer.
.ratingList: A list indexed by skill level, such that for skill level L, .ratingList[L] is the Rating object for that level. Level 0 is always ``no skill at all.''
.codeMap: A dictionary that maps the one-letter rating codes to the corresponding Rating object.
.exampleSkill: A string describing an example of a skill; the .example attributes of the Rating objects are related to this skill.
A Rating object describes a skill level within a RatingSystem. Exports:
Rating(level, code, title, meaning, example): Constructor. Arguments:
level: The level of the skill. Zero always means ``no skill.''
code: A one-letter code for this skill level.
title: The title of this skill as a string.
meaning: A string describing this skill level at length; may contain newlines.
example: A string describing this skill level for the skill type described in RatingSystem().exampleText; may contain newlines.
.code: As passed to the constructor.
.title: As passed to the constructor.
.meaning: As passed to the constructor.
.example: As passed to the constructor.
Here is the README file for this module, which comes from an older Python distribution, from a directory called Demo/pdist.
RCS module for Python - rcslib.py
---------------------------------
Module rcslib.py comes from the Python source distribution,
directory "Demo/pdist". It allows Python programs to perform
many common RCS function.
Warning: RCS functions are invoked through pipes using
os.popen(), so their output will go to stdout.
Usage
-----
+--
| import rcslib
| ...
| rcs = rcslib.RCS()
+--
Constructor. Only one instance is needed. The return object
represents the RCS files in the current directory (or in its
./RCS/ subdirectory).
+--
| rcs.log ( name_rev, otherflags='' )
+--
Returns the entire log file as a single string. Arguments:
* name_rev: Identifies the file to be checked out. This
argument may be either:
- a string, treated as the file name of the working file
in the current directory; or
- a tuple (name,rev) where name is the name of the working
file and rev is the revision number as a string (e.g.,
"1.14").
* otherflags: If given, this string is passed intact as
additional arguments to the "rlog" command.
+--
| rcs.head ( name_rev )
+--
Returns the head revision number for name_rev as a string. The
name_rev argument is the same as in the .log() method.
+--
| rcs.info ( name_rev )
+--
Returns a dictionary of information from "rlog -h". Here is
an example of the return value:
{'access list': '', 'total revisions': '3',
'RCS file': 'RCS/testfile,v', 'Working file': 'testfile',
'branch': '', 'symbolic names': '', 'head': '1.3',
'locks': 'strict', 'keyword substitution': 'kv'}
+--
| rcs.lock ( name_rev )
+--
Sets a lock on name_rev using "rcs -l".
+--
| rcs.unlock ( name_rev )
+--
Clears a lock on name_rev using "rcs -u".
+--
| rcs.checkout ( name_rev, withlock=0, otherflags='' )
+--
Checks out a file. Arguments:
* withlock: If 1, the file is checked out with a lock.
* otherflags: If a string is provided, that string is added as
command line arguments to the "co" command.
+--
| rcs.checkin ( name_rev, message=None, otherflags='' )
+--
Checks in a file. Arguments:
* message: The text of the check-in message. If this is the
initial check-in, it becomes the file description.
* otherflags: If a string is provided, that string is added as
command line arguments to the "ci" command.
+--
| rcs.listfiles ( pat=None )
+--
Returns a list of the working file names for files under RCS
control.
+--
| rcs.isvalid ( name )
+--
Predicate: Does the file with the given name have a version file?
+--
| rcs.rcsname ( name )
+--
Returns the relative pathname of the version file for the working
file with the given name.
+--
| rcs.realname ( namev )
+--
If namev is either a working file name or a version file name,
return the working file name.
+--
| rcs.islocked ( name_rev )
+--
Predicate: is name_rev locked? Works only if name_rev has a
version file.
+--
| rcs.checkfile ( name_rev )
+--
Raises an exception of there is no version file for name_rev.The xskill application uses a module from the author's standard Python library:
The Log module centralizes error message logging.
For more information about this module, see the documentation in file /u/john/tcc/python/lib/scan.tex.