Skip to content

Instantly share code, notes, and snippets.

@DaSalba
Last active August 25, 2025 19:50
Show Gist options
  • Select an option

  • Save DaSalba/1b4f8ab1e026fc00d3e9c1f571050e9c to your computer and use it in GitHub Desktop.

Select an option

Save DaSalba/1b4f8ab1e026fc00d3e9c1f571050e9c to your computer and use it in GitHub Desktop.
pfold (proper fold) is a small Python script that works like the Linux tool, but properly supports Unicode so lines with special characters will be have the correct length.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# "fold" is a well-known Linux tool that wraps a text file at N columns, and
# optionally splits lines at spaces with the -s option. Sadly it does not work
# well with accents and other special characters bigger than one byte, as it
# will incorrectly think that the line is longer than it really is.
# "pfold" (proper fold) strives to work correctly in the aforementioned cases by
# adding Unicode support (UTF-8). It has -w/--width and -s/--spaces switches.
# It will write the result to standard output just like "fold" does.
# Usage:
# pfold.py FILE [-s/--spaces] [-w/--width=WIDTH]
import argparse
from argparse import RawTextHelpFormatter # Show newlines in usage text.
USAGE_TEXT = (
"Wraps FILE at WIDTH columns (default 80), optionally breaking sentences at spaces.\n"
"The result will be written to standard output. Use redirects (>, >>) to save it.\n"
"Unlike Linux \"fold\", it correctly supports UTF-8 (like accented characters)."
)
def main():
argParser = argparse.ArgumentParser(description=USAGE_TEXT, formatter_class=RawTextHelpFormatter)
argParser.add_argument("file", help="the input file to process")
argParser.add_argument("-s", "--spaces", required=False, action='store_true', help="break lines at spaces")
argParser.add_argument("-w", "--width", required=False, dest="width", type=int, help="use WIDTH columns instead of 80", default=80)
args = argParser.parse_args()
try:
# Open the file.
with open(args.file, "r", encoding="utf-8") as inputFile:
# Read it line by line.
for line in inputFile:
# Prevent Windows line endings from being treated as two chars.
line = line.rstrip('\r\n')
# Initialize variables.
currentLine = ""
lastSpacePos = -1
# Read the line character by character.
for char in line:
# Have we retrieved N characters already?
if len(currentLine) == args.width:
# Do we have to wrap the line at spaces?
if args.spaces:
# Was there a space in this line?
if lastSpacePos == -1:
# If no, print it as it is.
print(currentLine)
# Begin a new line with the N+1 character.
currentLine = char
else:
# Split the line at that last space.
firstPart = currentLine[:lastSpacePos]
lastPart = currentLine[lastSpacePos:].lstrip()
print(firstPart)
# Begin a new line with the rest and N+1.
currentLine = lastPart + char
# Do we wrap anywhere, not just spaces?
else:
print(currentLine)
currentLine = char
lastSpacePos = -1
# Still less than N characters?
else:
# Add it to the line buffer and increment count.
currentLine += char
# if the character is a space, mark its position.
if char == ' ':
lastSpacePos = len(currentLine)
if currentLine:
print(currentLine)
else:
# Preserve existing newlines.
print()
except FileNotFoundError:
print(f"Error: The file '{args.file}' was not found.")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment