tomo/examples/wrap.tm

93 lines
3.3 KiB
Plaintext
Raw Normal View History

2024-09-05 12:37:33 -07:00
HELP := "
wrap: A tool for wrapping lines of text that are piped in through standard input
usage: wrap [--help] [--width=80] [--min_split=3] [--no-rewrap] [--hyphen='-']
--help: Print this message and exit
--width=N: The width to wrap the text
--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
--hyphen='...': The text to use for hyphenation
"
file := use ./file.tm
UNICODE_HYPHEN := Text.from_codepoint_names(["hyphen"])
func unwrap(text:Text, preserve_paragraphs=yes, hyphen="-")->Text:
if preserve_paragraphs:
paragraphs := text:split($/{2+ nl}/)
if paragraphs.length > 1:
return \n\n:join([unwrap(p, hyphen=hyphen, preserve_paragraphs=no) for p in paragraphs])
return text:replace($/$(hyphen)$(\n)/, "")
func wrap(text:Text, width:Int, min_split=3, hyphen="-")->Text:
if width <= 0:
fail("Width must be a positive integer, not $width")
if 2*min_split - hyphen.length > width:
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]
line := ""
for word in text:split($/{whitespace}/):
letters := word:split()
skip if letters.length == 0
while not _can_fit_word(line, letters, width):
line_space := width - line.length
if line != "": line_space -= 1
if min_split > 0 and line_space >= min_split + hyphen.length and letters.length >= 2*min_split:
# Split word with a hyphen:
split := line_space - hyphen.length
split = split _max_ min_split
split = split _min_ (letters.length - min_split)
if line != "": line ++= " "
line ++= ((++) letters:to(split)) ++ hyphen
letters = letters:from(split + 1)
else if line == "":
# Force split word without hyphenation:
if line != "": line ++= " "
line ++= ((++) letters:to(line_space))
letters = letters:from(line_space + 1)
else:
pass # Move to next line
lines:insert(line)
line = ""
if letters.length > 0:
if line != "": line ++= " "
line ++= (++) letters
if line != "":
lines:insert(line)
return \n:join(lines)
func _can_fit_word(line:Text, letters:[Text], width:Int; inline)->Bool:
if line == "":
return letters.length <= width
else:
return line.length + 1 + letters.length <= width
func main(width=80, min_split=3, rewrap=yes, hyphen="-"):
when file.read("/dev/stdin") is Failure(reason):
fail(reason)
is Success(text):
if rewrap:
text = unwrap(text)
first := yes
for paragraph in text:split($/{2+ nl}/):
if not first:
say(\n, newline=no)
say(wrap(paragraph, width=width, min_split=min_split, hyphen=hyphen))
first = no