tomo/examples/wrap/wrap.tm

103 lines
3.7 KiB
Plaintext
Raw Normal View History

2024-09-05 12:37:33 -07:00
HELP := "
2024-09-12 23:05:47 -07:00
wrap: A tool for wrapping lines of text
2024-09-05 12:37:33 -07:00
2024-09-12 23:05:47 -07:00
usage: wrap [--help] [files...] [--width=80] [--inplace=no] [--min_split=3] [--no-rewrap] [--hyphen='-']
2024-09-05 12:37:33 -07:00
--help: Print this message and exit
2024-09-12 23:05:47 -07:00
[files...]: The files to wrap (stdin is used if no files are provided)
2024-09-05 12:37:33 -07:00
--width=N: The width to wrap the text
--inplace: Whether or not to perform the modification in-place or print the output
2024-09-05 12:37:33 -07:00
--min-split=N: The minimum amount of text on either end of a hyphenation split
--rewrap|--no-rewrap: Whether to rewrap text that is already wrapped or only split long lines
2024-09-12 23:05:47 -07:00
--hyphen='-': The text to use for hyphenation
2024-09-05 12:37:33 -07:00
"
UNICODE_HYPHEN := "\{hyphen}"
2024-09-05 12:37:33 -07:00
2025-04-06 13:07:23 -07:00
func unwrap(text:Text, preserve_paragraphs=yes, hyphen=UNICODE_HYPHEN -> Text)
if preserve_paragraphs
paragraphs := text.split($/{2+ nl}/)
2025-04-06 13:07:23 -07:00
if paragraphs.length > 1
return "\n\n".join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs])
2024-09-05 12:37:33 -07:00
return text.replace("$(hyphen)\n", "")
2024-09-05 12:37:33 -07:00
2025-04-06 13:07:23 -07:00
func wrap(text:Text, width:Int, min_split=3, hyphen="-" -> Text)
if width <= 0
2024-09-05 12:37:33 -07:00
fail("Width must be a positive integer, not $width")
2025-04-06 13:07:23 -07:00
if 2*min_split - hyphen.length > width
2024-09-05 12:37:33 -07:00
fail("
Minimum word split length ($min_split) is too small for the given wrap width ($width)!
I can't fit a $(2*min_split - hyphen.length)-wide word on a line without splitting it,
... and I can't split it without splitting into chunks smaller than $min_split.
")
lines : @[Text]
2024-09-05 12:37:33 -07:00
line := ""
2025-04-06 13:07:23 -07:00
for word in text.split($/{whitespace}/)
letters := word.split()
2024-09-05 12:37:33 -07:00
skip if letters.length == 0
2025-04-06 13:07:23 -07:00
while not _can_fit_word(line, letters, width)
2024-09-05 12:37:33 -07:00
line_space := width - line.length
2025-04-06 13:49:40 -07:00
if line != "" then line_space -= 1
2024-09-05 12:37:33 -07:00
2025-04-06 13:07:23 -07:00
if min_split > 0 and line_space >= min_split + hyphen.length and letters.length >= 2*min_split
2024-09-05 12:37:33 -07:00
# Split word with a hyphen:
split := line_space - hyphen.length
split = split _max_ min_split
split = split _min_ (letters.length - min_split)
2025-04-06 13:49:40 -07:00
if line != "" then line ++= " "
line ++= ((++: letters.to(split)) or "") ++ hyphen
letters = letters.from(split + 1)
2025-04-06 13:07:23 -07:00
else if line == ""
2024-09-05 12:37:33 -07:00
# Force split word without hyphenation:
2025-04-06 13:49:40 -07:00
if line != "" then line ++= " "
line ++= (++: letters.to(line_space)) or ""
letters = letters.from(line_space + 1)
2025-04-06 13:07:23 -07:00
else
2024-09-05 12:37:33 -07:00
pass # Move to next line
lines.insert(line)
2024-09-05 12:37:33 -07:00
line = ""
2025-04-06 13:07:23 -07:00
if letters.length > 0
2025-04-06 13:49:40 -07:00
if line != "" then line ++= " "
2024-11-05 12:20:47 -08:00
line ++= (++: letters) or ""
2024-09-05 12:37:33 -07:00
2025-04-06 13:07:23 -07:00
if line != ""
lines.insert(line)
2024-09-05 12:37:33 -07:00
return "\n".join(lines)
2024-09-05 12:37:33 -07:00
2025-04-06 13:07:23 -07:00
func _can_fit_word(line:Text, letters:[Text], width:Int -> Bool; inline)
if line == ""
2024-09-05 12:37:33 -07:00
return letters.length <= width
2025-04-06 13:07:23 -07:00
else
2024-09-05 12:37:33 -07:00
return line.length + 1 + letters.length <= width
2025-04-06 13:07:23 -07:00
func main(files:[Path], width=80, inplace=no, min_split=3, rewrap=yes, hyphen=UNICODE_HYPHEN)
if files.length == 0
files = [(/dev/stdin)]
2025-04-06 13:07:23 -07:00
for file in files
text := file.read() or exit("Could not read file: $file")
2025-04-06 13:07:23 -07:00
if rewrap
2024-09-05 12:37:33 -07:00
text = unwrap(text)
2025-04-06 13:07:23 -07:00
out := if file.is_file() and inplace
file
2025-04-06 13:07:23 -07:00
else
(/dev/stdout)
2024-09-05 12:37:33 -07:00
first := yes
wrapped_paragraphs : @[Text]
2025-04-06 13:07:23 -07:00
for paragraph in text.split($/{2+ nl}/)
wrapped_paragraphs.insert(
wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen)
)
out.write("\n\n".join(wrapped_paragraphs[]) ++ "\n")