DESCRIPT.ION support in Monad -- Part 3

Thu, Oct 13, 2005 5-minute read

Last time, we wrote code to refresh our description data while we navigate the file system.  We hadn’t yet integrated it to our directory listings, so let’s do that now.

Step 3: Customize the format.mshxml for directory listings

Our final step is to customize the formatting information that Monad uses to output a directory listing.  This is a fairly small change that mimics the way we output the “LastWriteTime” column.

First, open “FileSystem.format.mshxml” from your Monad installation directory.  This file tells Monad how convert the raw .Net File System objects into a form more suitable for humans.  The tweaking we are about to do illustrates one of the fundamental powers of Monad.  Data in the pipeline retains its full object-oriented fidelity until it reaches your screen.  This final stage of the pipeline is called “Formatting and Output.”

Now, before we customize our directory listing, let’s go over the different parts of the formatting file to see what it does.

The formatting files are XML.  The root node contains the following high-level configuration nodes: SelectionSets, Controls, and ViewDefinitions.

<Configuration>  
    <SelectionSets>  
        <SelectionSet>  
           ...  
        </SelectionSet>  
    </SelectionSets>

    <!-- # GLOBAL CONTROL DEFINITIONS # -->  
    <Controls>  
        <Control>  
           ...  
        </Control>  
    </Controls>  
     
    <!-- # VIEW DEFINITIONS # -->  
    <ViewDefinitions>  
       <View>  
          ...  
       </View>  
       <View>  
          ...  
       </View>  
    </ViewDefinitions>  
</Configuration>

The SelectionSets node lists a set of .Net types.  Later, the custom views from this file use this SelectionSet to help Monad determine the object types to which Monad should apply this formatting information.

The Controls node defines a custom header.  Later, custom views from this file use this header on groups of file system objects.

The Views form the real core of this file:

<ViewDefinitions>  
   <View>  
      <Name>children</Name>  
      <ViewSelectedBy>  
         ...  
      </ViewSelectedBy>  
      <GroupBy>  
         ...  
      </GroupBy>  
      <TableControl>  
         <TableHeaders>  
            ...  
         </TableHeaders>  
         <TableRowEntries>  
            ...  
         </TableRowEntries>  
      </TableControl>  
   </View>  
   <View>  
      ...  
      <ListControl>  
         ...  
      </ListControl>  
   </View>  
   <View>  
      ...  
      <WideControl>  
         ...  
      </WideControl>  
   </View>  
</ViewDefinitions>

We start by naming the view, and then use the ViewSelectedBy node to reference the list of .Net objects to which this view applies.  This reference is the name of one of the SelectionSets nodes earlier in the file.  Next, the GroupBy node tells Monad how to group the objects.  For example, a directory listing is grouped by its parent.  Each grouping gets a header, as defined by one of the Controls earlier in the file.  This is most evident when you do a recursive directory listing, as in “dir –rec.”

Then, we define three views: a TableControl, a ListControl, and a WideControl.  These correspond to the following statements, respectively:

dir | format-table  
dir | format-list  
dir | format-wide

We’ll focus on (and customize) only the TableControl.

A TableControl has a header row, followed by data rows.  We define the header format with a sequence of TableColumnHeader nodes, then the row format with a sequence of TableRowEntry nodes.

Headers have a label, width, and alignment.  In this file, row entries reference either a direct property of the underlying object, or a ScriptBlock.  As illustrated by the LastWriteTime entry, ScriptBlocks reference the current pipeline object using the “$_” automatic variable.

When Monad loads its formatting data, the first file to define a view wins.  Ours will define the TableControl for DirectoryInfo objects first, so it will override the view that the default FileSystem.format.mshxml defines.

To make Monad look at our file before the default, add the following lines to your profile.msh:

## Update the formatting XML, overriding one of the filesystem
## views with the one we define in FileSystem.Description.Format.mshxml
$formatFile = "$(parse-path $profile)\FileSystem.Description.Format.mshxml"
update-FormatData -prependpath $formatFile

Now, copy the FileSystem.format.mshxml into the same directory as your profile, but call it FileSystem.Description.format.mshxml.  After you copy, change its read-only bit so that you can edit it.  Open the file, now at “My Documents”\msh\FileSystem.Description.format.mshxml.  We’ll place our customizations in this file, rather than hacking on the one that ships with Monad. 

For a test, go to a temporary directory and create a file with a very long name:

MSH:7 C:\Temp >"hi" > ("a"*200)
MSH:8 C:\Temp >dir

    Directory: FileSystem::C:\Temp

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         10/9/2005   3:16 PM          4 aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaaaaaaaaaaaa
                                             aaaa

In your custom formatting file, delete the <wrap /> node, reload MSH, and then do another listing in that directory:

MSH:2 C:\Temp >dir

    Directory: FileSystem::C:\Temp

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         10/9/2005   3:16 PM          4 aaaaaaaaaaa...

You can delete the long file now, as we’re done with it.

Let’s finally get our description column into directory listings.  In your custom formatting file, do the following:

  1. Delete the SelectionSets node, and all of its children.  The defaults will serve us just fine.
  2. Delete the Controls node, and all of its children.  The defaults will serve us just fine.
  3. Delete the View node that contains the ListControl.
  4. Delete the View node that contains the WideControl.
  5. Change the width of the LastWriteTime header to 20.
  6. After the last Table Column Header (labeled “”,) create a new TableColumnHeader node:
    <TableColumnHeader>  
       <Label>Description</Label>  
       <Alignment>left</Alignment>  
       <Width>30</Width>  
    </TableColumnHeader>
  1. After the last Table Column Item (the one for “Name,”) create a new TableColumnItem node:
    <TableColumnItem>  
       <ScriptBlock>  
          ${lee.holmes.descriptions}\[$\_.Name\]  
       </ScriptBlock>  
    </TableColumnItem>
  1. Your file should now look like this [FileSystem.Description.Format.mshxml]

  2. Create a file called “DESCRIPT.ION” in your profile directory.  Place the following in it:

    DESCRIPT.ION Descriptions file
    FileSystem.Description.Format.mshxml Formatting customizations
    profile.msh MSH custom profile

  3. Restart your MSH shell, navigate to your profile directory, and get a directory listing:

And there you have it.  DESCRIPT.ION support in Monad.

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]