This PR adds support to the Python port for generating text-mode diagrams as an alternative to SVG. The diagrams can be drawn with either pure-ASCII or Unicode (U+25xx) characters, or with any characters of the user's choosing. I have compared the SVG and text-mode output for all test.py test cases visually, and they all look correct to me.
If this PR is merged, I'll look into porting the final accepted code to the JavaScript version.
Feel free to comment on the code and recommend changes - nobody has seen it before but me :-)
Notes
All changes are to railroad.py and test.py.
No existing code was altered, except in __main__ (see below).
The text format code never alters any data in the diagram classes, and so never affects the behavior of the SVG format code.
A number of new test cases were added to test.py, to verify correct operation of the text-mode diagrams.
New class TextDiagram added:
A TextDiagram is a rectangular object of characters, with the following fields:
lines: The array of characters, in the form of a list of strings.
Each string is a horizontal row.
lines[0] is the topmost row, and lines[0][0] is the top-left-most character.
height: The height of the rectangle, in characters.
width: The width of the rectangle, in characters.
entry: The index into lines of the row where the surrounding diagram enters this sub-diagram.
exit: The index into lines of the row where this sub-diagram exits to the surrounding diagram.
TextDiagrams are intended to be immutable.
The code never alters them, although Python doesn't really support immutable class objects.
TextDiagram is not intended to by used by users of railroad, only by the diagram element classes.
All non-instance methods are named starting with an underscore.
Some are static methods, others are class methods.
They all return something appropriate to their name, usually not a TextDiagram.
Most are intended only for internal-use by TextDiagram, but there are a few exceptions:
_gaps(outerWidth, innerWidth): Similar to determineGaps(), but measured in characters and therefore dealing with possibly-odd splits.
_getParts(partNames): Returns one or more drawing characters by name (see "parts" below).
_maxWidth(*args): Returns the maximum width of all of its arguments, which may be ints, strs, lists of strs, or TextDiagrams.
All other methods are "API" methods.
They are all named like something or somethingElse.
They all return a new TextDiagram, based on the instance, except for setFormatting().
setFormatting() controls the formatting via its parameters, and returns None.
I originally expected there to be more to this, but right now it's just the parts.
It is possible, through setFormatting(), to specify a custom set of drawing characters, or to override some of the standard characters.
Some of them are static factory methods, creating a new TextDiagram based on their parameters.
Most are instance methods, returning a new TextDiagram based on the instance.
TextDiagram has three class variables:
parts: A dictionary of named drawing characters. All the code draws the diagrams by obtaining the characters by name from this dictionary. It is initialized from one of the two pre-defined sets, or can be replaced by setFormatting().
PARTS_ASCII: A dictionary of drawing characters restricted to the ASCII 7-bit character set.
PARTS_UNICODE: A dictionary of drawing characters from the Unicode character set, based primarily, but not exclusively, on the 25xx box-drawing plane.
New Diagram.writeText() instance method added to create and output text diagrams. API is identical to Diagram.writeSvg().
Unless this method is called, none of the text format code ever gets executed.
New instance method textDiagram() added to each diagram element:
Takes no parameters, and returns a TextDiagram for the element, including all the elements it contains.
New option-constant:
ESCAPE_HTML: a boolean constant used by Diagram.writeText() to decide whether to output raw characters or to replace "<", ">", '"', and "&" with their HTML-entity equivalents.
It might be a better idea to make it a parameter to writeText(), but I wanted writeText()'s API to mimicwriteSvg()`'s.
I made several changes to the __main__ code, which you can accept or discard as you see fit:
Command-line support for specifying the output mode and for filtering the list of test.py diagrams to process.
Syntax is: railroad.py MODE TESTNAME1 TESTNAME2 ....
MODE is svg, standalone, ascii, or unicode, case-insensitively. If not specified, it defaults to svg.
If no TESTNAMEs are specified, all tests are processed, Otherwise, only the named tests are processed.
Tests are always processed in the order they are defined in test.py.
Since all arguments are optional, typing just railroad.py produces SVG output of all the tests, just like before.
This PR adds support to the Python port for generating text-mode diagrams as an alternative to SVG. The diagrams can be drawn with either pure-ASCII or Unicode (U+25xx) characters, or with any characters of the user's choosing. I have compared the SVG and text-mode output for all
test.py
test cases visually, and they all look correct to me.If this PR is merged, I'll look into porting the final accepted code to the JavaScript version.
Feel free to comment on the code and recommend changes - nobody has seen it before but me :-)
Notes
railroad.py
andtest.py
.__main__
(see below).test.py
, to verify correct operation of the text-mode diagrams.TextDiagram
added:TextDiagram
is a rectangular object of characters, with the following fields:lines
: The array of characters, in the form of a list of strings.lines[0]
is the topmost row, andlines[0][0]
is the top-left-most character.height
: The height of the rectangle, in characters.width
: The width of the rectangle, in characters.entry
: The index intolines
of the row where the surrounding diagram enters this sub-diagram.exit
: The index intolines
of the row where this sub-diagram exits to the surrounding diagram.TextDiagram
s are intended to be immutable.TextDiagram
is not intended to by used by users ofrailroad
, only by the diagram element classes.TextDiagram
.TextDiagram
, but there are a few exceptions:_gaps(outerWidth, innerWidth)
: Similar todetermineGaps()
, but measured in characters and therefore dealing with possibly-odd splits._getParts(partNames)
: Returns one or more drawing characters by name (see "parts
" below)._maxWidth(*args)
: Returns the maximum width of all of its arguments, which may beint
s,str
s, lists ofstr
s, orTextDiagram
s.something
orsomethingElse
.TextDiagram
, based on the instance, except forsetFormatting()
.setFormatting()
controls the formatting via its parameters, and returnsNone
.parts
.setFormatting()
, to specify a custom set of drawing characters, or to override some of the standard characters.TextDiagram
based on their parameters.TextDiagram
based on the instance.TextDiagram
has three class variables:parts
: A dictionary of named drawing characters. All the code draws the diagrams by obtaining the characters by name from this dictionary. It is initialized from one of the two pre-defined sets, or can be replaced bysetFormatting()
.PARTS_ASCII
: A dictionary of drawing characters restricted to the ASCII 7-bit character set.PARTS_UNICODE
: A dictionary of drawing characters from the Unicode character set, based primarily, but not exclusively, on the 25xx box-drawing plane.Diagram.writeText()
instance method added to create and output text diagrams. API is identical toDiagram.writeSvg()
.textDiagram()
added to each diagram element:TextDiagram
for the element, including all the elements it contains.ESCAPE_HTML
: a boolean constant used byDiagram.writeText()
to decide whether to output raw characters or to replace "<", ">", '"', and "&" with their HTML-entity equivalents.writeText()
, but I wanted writeText()'s API to mimic
writeSvg()`'s.__main__
code, which you can accept or discard as you see fit:test.py
diagrams to process.railroad.py MODE TESTNAME1 TESTNAME2 ...
.MODE
issvg
,standalone
,ascii
, orunicode
, case-insensitively. If not specified, it defaults tosvg
.TESTNAME
s are specified, all tests are processed, Otherwise, only the named tests are processed.test.py
.railroad.py
produces SVG output of all the tests, just like before.