How embel.py works

The python code for the embel python script is split in two parts:

  • "utility" functions are kept in embellib.py
  • the actual work is done by embel.py calling those functions

The separation has nothing to do with generalization of the utility library. Both the embel.py and the embellib.py code is specific to the problem at hand. The only reason for the existence of embellib.py is convenience for unit-testing. To run embellib.py's unit-tests, simply call the script with no parameters:

embel.py

The unit-tests require the existence of the dummy and subdummy subdirectories:

subdummy is empty and only used for path-testing.
dummy should contain these files for testing:

foobar.txt
foobarcopy.txt
lines-source.txt
lines-target-orig.txt
lines-target.txt
source.txt
subdummy
target-orig.txt
target.txt
embellib.py

There are two central utility functions in embellib.py, each representing a method from transplanting code from one file to the other:

  • ReplaceLineStartsWith looks for a line with a certain beginning in the target text and replaces it with a line with the same beginning from the source text. We use ReplaceLineStartsWith for embellishing single lines.
  • TransplantCenter expects two identical sets of markers in both a source text and a target text. Everything between those markers is "transplanted" from the source to the target. We use TransplantCenter for embellishing multi-line sections.
Understanding ReplaceLineStartsWith

Example: The unembellished code for identifying the Edit command in SearchResultLocationControl.ascx.cs is

if (e.Column.ItemID == "Edit")

The embellished code is

if (e.Column.ItemID == "Edit" || e.Column.ItemID == "LeftColumnEdit")

We call FileWriteReplaceLineStartsWith with three parameters:

  • the path to the source project file with the embellished line
  • the target project file with the unebellished line
  • the start of the line if (e.Column.ItemID == "Edit"

(FileWriteReplaceLineStartsWith in turn calls ReplaceLineStartsWith.)

Here is an illustration:

The actual call in this case would be

FileWriteReplaceLineStartsWith ("C:\\PhoneBook-Embellished\\PhoneBook.Web\\UI\\SearchResultLocationControl.ascx.cs", 
"C:\\PhoneBook\\PhoneBook.Web\\UI\\SearchResultLocationControl.ascx.cs", 
'if (e.Column.ItemID == "Edit"')

This particular function, FileWriteReplaceLineStartsWith, overwrites the target file. It delegates the actual work of substitution to a similar function, FileReplaceLineStartsWith. This function in turn reads in the source- and target file and delegates substitution to the purely text-based function ReplaceLineStartsWith. This pattern of file writer -> file reader -> actual work on read in texts is repeated in the next set of functions – TransplantCenter

lines are stripped for matching

Before matching each line, ReplaceLineStartsWith strips away leading white-space. However, this is only for detection (matching) of the startsWith. The actual substitution will copy leading white-space.

Understanding TransplantCenter

TransplantCenter uses the function ThreeSplit to split a text into three parts:

  • before the begin marker
  • after begin marker, before end marker
  • after end marker

What is important here is that the markers themselves are always part of the "center". Illustration of what ThreeSplit does:

FileWriteTransplantCenter overwrites the center from the source file with the center in the target file:

We can make good use of FileWriteTransplantCenter for solving the DisplayName problem explained in section "Getting rid of display names" in the PhoneBook tutorial. To that end, we have to replace the fixed columns in the BocList. The unembellished fixed columns in SearchResultXControl.ascx s always look like this (remember the example from the embel overview?)

    <FixedColumns>
      <remotion:BocAllPropertiesPlaceholderColumnDefinition />
      <remotion:BocCommandColumnDefinition ItemID="Edit" Text="$res:Edit">
        <PersistedCommand>
          <remotion:BocListItemCommand Type="Event" />
        </PersistedCommand>
      </remotion:BocCommandColumnDefinition>
    </FixedColumns>

For the SearchResultLocationControl.ascx the embellished version is:

      <FixedColumns>
        <remotion:BocSimpleColumnDefinition EnableIcon="True" 
          PropertyPathIdentifier="Street" ItemID="LeftColumnEdit">
          <persistedcommand>
            <remotion:BocListItemCommand Type="Event" />
          </persistedcommand>
        </remotion:BocSimpleColumnDefinition>
        <remotion:BocSimpleColumnDefinition PropertyPathIdentifier="Number" />
        <remotion:BocSimpleColumnDefinition PropertyPathIdentifier="City" />
        <remotion:BocSimpleColumnDefinition PropertyPathIdentifier="Country" />
        <remotion:BocSimpleColumnDefinition PropertyPathIdentifier="ZipCode" />
        <remotion:BocCommandColumnDefinition ItemID="Edit" Text="$res:Edit">
          <PersistedCommand>
            <remotion:BocListItemCommand Type="Event" />
          </PersistedCommand>
        </remotion:BocCommandColumnDefinition>
        <remotion:BocCommandColumnDefinition ItemID="Delete" Text="$res:Delete">
          <PersistedCommand>
            <remotion:BocListItemCommand Type="Event" />
          </PersistedCommand>
        </remotion:BocCommandColumnDefinition>
      </FixedColumns>

Using <FixedColumns> and </FixedColumns> as begin- end end markers, respectively, we can call FileWriteTransplantCenter for embellishing SearchResultLocationControl.ascx:

FileWriteTransplantCenter ("C:\\PhoneBook-Embellished\\PhoneBook.Web\\UI\SearchResultLocationControl.ascx.cs", 
"C:\\PhoneBook\\PhoneBook.Web\\UI\\SearchResultLocationControl.ascx.cs", 
"<FixedColumns>", 
"</FixedColumns>")

As with ReplaceLineStartsWith, we have a "tiered" logic of functions:

  • FileWriteTransplantCenter overwrites the target file
  • FileTransplantCenter does the actual substitution for texts from source- and target file, but does not write anything
  • TransplantCenter does the actual substitution on read-in texts
Markers

Some code elements lend themselves quite naturally as markers. The <FixedColumns> and </FixedColumns> from the previous example are only one of several instances. The others are:

  • <WxeFunction>, </WxeFunction> for embellishing WXE headers
  • <remotion:BocReferenceValue, </remotion:BocReferenceValue> for adding the New location, Pick location action menu items

All these begin- and end markers can be found in the code, because they actually serve other purposes than serving as begin- or end markers. For those markers, variables (supposed to be constants) are declared in embellib.py.

Other markers come not so easy. In order to be able to substitute embellishments for each method (or property), we use the type- and method (or property) name as begin marker. The end marker must be supplemented artificially; that's what the // END ... comments are for. These markers are
now part of the uigen.exe templates and thus generated for the (unembellished) application.

Examples for such markers:

kBeginUserControlMultiView = "TabbedMultiView UserControlMultiView" # a property delcaration serving as marker
kEndUserControlMultiView = "// END UserControlMultiView"         
kBeginSaveButtonClick = "void SaveButton_Click"                     # a method name serving as marker
kEndSaveButtonClick = "// END SaveButton_Click"

In this fashion, all method or property declarations annotated with such comments can be embellished by embel.py.

Another case is where entire sections in a class declaration, not only single method- or property declarations must be embellished. The markers

kBeginCustom = "// BEGIN CUSTOM"
kEndCustom = "// END CUSTOM

serve precisely this purpose, and they are included in the source files by uigen.exe (or, are written into the templates).

embel.py

embel.py uses the functions explained above. In embel.py, specialized functions for embellishing fixed columns, event handlers and other code are declared and called.