aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--screenplain/richstring.py64
-rw-r--r--tests/richstring_test.py6
2 files changed, 49 insertions, 21 deletions
diff --git a/screenplain/richstring.py b/screenplain/richstring.py
index e126ae4..c602966 100644
--- a/screenplain/richstring.py
+++ b/screenplain/richstring.py
@@ -203,6 +203,32 @@ underline = _CreateStyledString((Underline,))
empty_string = RichString()
+# A special unicode character to use for a literal '*'
+literal_star = u'\ue706'
+
+# All styles. Note: order matters! This is the order they are parsed.
+all_styles = (Bold, Italic, Underline)
+
+
+def _unescape(source):
+ """Converts backslash-escaped stars in a string to the magic
+ "literal star" character.
+
+ >>> _unescape(r'\*hello\*')
+ u'\ue706hello\ue706'
+
+ """
+ return source.replace('\\*', literal_star)
+
+
+def _demagic_literals(text):
+ """Converts "literal star" characters to actual stars: "*"
+
+ >>> _demagic_literals(u'\ue706hello\ue706')
+ u'*hello*'
+ """
+ return text.replace(literal_star, '*')
+
def parse_emphasis(source):
"""Parses emphasis markers like * and ** in a string
@@ -215,15 +241,18 @@ def parse_emphasis(source):
>>> parse_emphasis(u'**hello** there')
(bold)(u'hello') + (plain)(u' there')
"""
- source = Bold.parse_re.sub(
- Bold.start_magic + r'\1' + Bold.end_magic, source
- )
- source = Italic.parse_re.sub(
- Italic.start_magic + r'\1' + Italic.end_magic, source
- )
- source = Underline.parse_re.sub(
- Underline.start_magic + r'\1' + Underline.end_magic, source
- )
+
+ # Convert escaped characters to magic characters so they aren't parsed
+ # as emphasis.
+ source = _unescape(source)
+
+ for style in all_styles:
+ source = style.parse_re.sub(
+ style.start_magic + r'\1' + style.end_magic, source
+ )
+
+ # Convert magic characters back, so they are printable again.
+ source = _demagic_literals(source)
styles = set()
segments = []
@@ -240,18 +269,11 @@ def parse_emphasis(source):
append(pos, end)
pos = end + 1
magic = match.group(0)
- if magic == Bold.start_magic:
- styles.add(Bold)
- elif magic == Bold.end_magic:
- styles.remove(Bold)
- elif magic == Italic.start_magic:
- styles.add(Italic)
- elif magic == Italic.end_magic:
- styles.remove(Italic)
- elif magic == Underline.start_magic:
- styles.add(Underline)
- elif magic == Underline.end_magic:
- styles.remove(Underline)
+ for style in all_styles:
+ if magic == style.start_magic:
+ styles.add(style)
+ elif magic == style.end_magic:
+ styles.remove(style)
append(pos, len(source))
return RichString(*segments)
diff --git a/tests/richstring_test.py b/tests/richstring_test.py
index 0e83aa1..5e5f7f5 100644
--- a/tests/richstring_test.py
+++ b/tests/richstring_test.py
@@ -199,3 +199,9 @@ class ParseEmphasisTests(unittest2.TestCase):
parse_emphasis('**first** **second**'),
bold('first') + plain(' ') + bold('second')
)
+
+ def test_escaping_star_creates_a_literal_star(self):
+ self.assertEquals(
+ parse_emphasis(r'\*hello*'),
+ plain('*hello*')
+ )