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 useReplaceLineStartsWith
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 useTransplantCenter
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 fileFileTransplantCenter
does the actual substitution for texts from source- and target file, but does not write anythingTransplantCenter
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.