# Buggy code
def __init__(self, sourcef, incfile=False, widthcount=0):
    """Process a file and decorate the resultant Preprocess instance with
    self.result (the preprocessed file) and self.styles (extracted stylesheet
    information) for the caller.
    """

    # fix keywords dict for use by the parser.
    self.keywords = dict(
        [(x + "::", getattr(self, "handle_" + x)) for x in self.keywords]
    )

    self.widthcount = widthcount

    name = sourcef.name
    source = sourcef.read()
    if isinstance(source, bytes):
        source = source.decode("utf8")
    source = source.replace("\r\n", "\n").replace("\r", "\n")

    # Make the determination if an include file is a stylesheet or
    # another restructured text file, and handle stylesheets appropriately.

    if incfile:
        try:
            self.styles = styles = rson_loads(source)
            substyles = styles.get("styles")
            if substyles is not None:
                styles["styles"] = dict(substyles)
        except:
            pass
        else:
            self.changed = True
            self.keep = False
            return

    # Read the whole file and wrap it in a DummyFile
    self.sourcef = DummyFile(source)
    self.sourcef.name = name

    # Use a regular expression on the source, to take it apart
    # and put it back together again.

    self.source = source = [x for x in self.splitter(source) if x]
    self.result = result = []
    self.styles = {}
    self.changed = False

    # More efficient to pop() a list than to keep taking tokens from [0]
    source.reverse()
    isblank = False
    keywords = self.keywords
    handle_single = keywords["single::"]
    while source:
        wasblank = isblank
        isblank = False
        chunk = source.pop()
        result.append(chunk)

        # Only process single lines
        if not chunk.endswith("\n"):
            continue
        result[-1] = chunk[:-1]
        if chunk.index("\n") != len(chunk) - 1:
            continue

        # Parse the line to look for one of our keywords.
        tokens = chunk.split()
        isblank = not tokens
        if len(tokens) >= 2 and tokens[0] == ".." and tokens[1].endswith("::"):
            func = keywords.get(tokens[1])
            if func is None:
                continue
            chunk = chunk.split("::", 1)[1]
        elif (
            wasblank and len(tokens) == 1 and chunk[0].isalpha() and tokens[0].isalpha()
        ):
            func = handle_single
            chunk = tokens[0]
        else:
            continue

        result.pop()
        func(chunk.strip())

    # Determine if we actually did anything or not.  Just use our source file
    # if not.  Otherwise, write the results to disk (so the user can use them
    # for debugging) and return them.
    if self.changed:
        result.append("")
        result = DummyFile("\n".join(result))
        result.name = name + ".build_temp"
        self.keep = keep = len(result.strip())
        if keep:
            f = open(result.name, "w")
            f.write(result)
            f.close()
        self.result = result
    else:
        self.result = self.sourcef


# Traceback (UnicodeEncodeError)
$ rst2pdf test.rst -e preprocess -v
[INFO] createpdf.py:1578 Importing extension module 'vectorpdf'
[INFO] createpdf.py:1578 Importing extension module 'preprocess'
Traceback (most recent call last):
File "/Users/rob/.pyenv/versions/rst2pdf-2/bin/rst2pdf", line 11, in <module>
load_entry_point('rst2pdf', 'console_scripts', 'rst2pdf')()
File "/Users/rob/Projects/python/rst2pdf/rst2pdf/createpdf.py", line 1461, in main
add_extensions(options)
File "/Users/rob/Projects/python/rst2pdf/rst2pdf/createpdf.py", line 1592, in add_extensions
module.install(createpdf, options)
File "/Users/rob/Projects/python/rst2pdf/rst2pdf/extensions/preprocess_r2p.py", line 367, in install
data = Preprocess(options.infile)
File "/Users/rob/Projects/python/rst2pdf/rst2pdf/extensions/preprocess_r2p.py", line 153, in __init__
self.sourcef = DummyFile(source)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 44-46: ordinal not in range(128)

# Fixed code
def __init__(self, sourcef, incfile=False, widthcount=0):
    """Process a file and decorate the resultant Preprocess instance with
    self.result (the preprocessed file) and self.styles (extracted stylesheet
    information) for the caller.
    """

    # fix keywords dict for use by the parser.
    self.keywords = dict(
        [(x + "::", getattr(self, "handle_" + x)) for x in self.keywords]
    )

    self.widthcount = widthcount

    name = sourcef.name
    source = sourcef.read()
    if isinstance(source, bytes):
        source = source.decode("utf8")
    source = source.replace("\r\n", "\n").replace("\r", "\n")

    # Make the determination if an include file is a stylesheet or
    # another restructured text file, and handle stylesheets appropriately.

    if incfile:
        try:
            self.styles = styles = rson_loads(source)
            substyles = styles.get("styles")
            if substyles is not None:
                styles["styles"] = dict(substyles)
        except:
            pass
        else:
            self.changed = True
            self.keep = False
            return

    # Read the whole file and wrap it in a DummyFile
    self.sourcef = DummyFile(name, source)

    # Use a regular expression on the source, to take it apart
    # and put it back together again.
    self.source = source = [x for x in self.splitter(source) if x]
    self.result = result = []
    self.styles = {}
    self.changed = False

    # More efficient to pop() a list than to keep taking tokens from [0]
    source.reverse()
    isblank = False
    keywords = self.keywords
    handle_single = keywords["single::"]
    while source:
        wasblank = isblank
        isblank = False
        chunk = source.pop()
        result.append(chunk)

        # Only process single lines
        if not chunk.endswith("\n"):
            continue
        result[-1] = chunk[:-1]
        if chunk.index("\n") != len(chunk) - 1:
            continue

        # Parse the line to look for one of our keywords.
        tokens = chunk.split()
        isblank = not tokens
        if len(tokens) >= 2 and tokens[0] == ".." and tokens[1].endswith("::"):
            func = keywords.get(tokens[1])
            if func is None:
                continue
            chunk = chunk.split("::", 1)[1]
        elif (
            wasblank and len(tokens) == 1 and chunk[0].isalpha() and tokens[0].isalpha()
        ):
            func = handle_single
            chunk = tokens[0]
        else:
            continue

        result.pop()
        func(chunk.strip())

    # Determine if we actually did anything or not.  Just use our source file
    # if not.  Otherwise, write the results to disk (so the user can use them
    # for debugging) and return them.
    if self.changed:
        result.append("")
        result = DummyFile(name + ".build_temp", "\n".join(result))
        self.keep = keep = len(result.read().strip())
        if keep:
            f = open(result.name, "w")
            # Can call read a second time here because it's a DummyFile:
            f.write(result.read())
            f.close()
        self.result = result
    else:
        self.result = self.sourcef