I'm a big fan of plain text files. Years may pass, new powerful file formats and editors may be released, but they'll never replaced the good, old .txt in my heart. There's this feeling I can't really describe when I see the austere GUI, the monospaced font and the lines of neatly wrapped text on a white background. There's nothing superfluous to distract you. Writing like this forces you to be really focused and compose a mental image, or map, of what you want to convey. From the veteran Windows Notepad to Linux's gedit and its derivatives, I've spent thousands of hours staring at editors like these.

And yes, the text MUST be wrapped at 80 columns. I can't accept it otherwise. I don't care if it's 2025 already! I used to wrap it myself, until I discovered a nice little *nix utility named fold. This command will do it for you, but there's a problem: it doesn't work correctly with special characters such as accents. The tool will erroneously think that there are two separate characters and the column count will be wrong. Nothing surprising here; it's an ancient tool. So, how to fix this?

Well, isn't it convenient that I happened to write a Python replacement that properly supports those characters? pfold (proper fold) will work the same way as fold otherwise.

Download

Code

#!/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()