Pronounced "ROSE-edd". Fluent text editing and layout library for Go.
This library treats text as a sequence of "grapheme clusters" (user-visible characters) as opposed to actual runes or bytes. This allows wrapping and other character-counting/index operations to function in an expected way regardless of whether visible characters are made up of Unicode decomposed sequences or pre-composed sequences.
Take something like "Ý", which appears to be one single character but behind the scenes could be a single precomposed character rune or multiple runes that compose into a single apparent character using Unicode combining marks, such as the Unicode codepoint for uppercase "Y" (U+0079) followed by the codepoint for the combining acute accent (U+0301) (not to be confused with the non-combining codepoint for the acute accent symbol by itself, U+00B4. Naturally. Isn't Unicode fun?).
Regardless of its representation in runes, we as humans see the Y with an acute accent and will usually say "ah, that is one character", and that is a grapheme cluster (or just grapheme for short when referring to the thing being displayed as opposed to the runes that make up that thing). This library takes that opinion as well, and will operate on text in a consistent way regardless of how each grapheme is represented.
You can find out more information on the Unicode definition of "grapheme cluster" under Chapter 2 of the Unicode Standard, in particular see the heading '"Characters" and Grapheme Clusters" at the end of section 2.11, "Combining Characters". If you want, you can also dig into section 2.12, "Equivalant Sequences", to see how different grapheme clusters (codepoint sequences) can translate into the same visual grapheme.
- Performant - Performance is slowly improved with releases but this library aims to be easy to use as its first goal.
- Collating - This libarary does not support Unicode collation. It may come at a later date.
This library aims to make it easy to work with text in a CLI environment, or
really any that has fixed-width text in it. This section shows some of the
things you can do with rosed, but by no means is it all of it. Check the
Full Reference Docs for a
listing of all available functions as well as examples of use.
In general, to use rosed, first create an Editor, use it to modify text, then get it back by converting it to a string. As easy as plucking a rose from the bed of thorns that grows in the midst of the most eldritch of gardens. Perhaps easier, in fact.
You can use rosed to wrap text to a certain width:
text := "It's hard, being a kid and growing up. It's hard and nobody understands."
wrapped := rosed.Edit(text).Wrap(30).String()
fmt.Println(wrapped)Output:
It's hard, being a kid and
growing up. It's hard and
nobody understands.
You can do this even if it's already been hard-wrapped:
text := "It's hard, being\na kid and growing up.\nIt's hard and nobody\nunderstands."
wrapped := rosed.Edit(text).Wrap(30).String()
fmt.Println(wrapped)You can use rosed to ensure that all lines in the text take up the same width, and add spacing equally between words on each line to ensure this is the case. This is also known as justifying a block of text:
text := "I WARNED YOU ABOUT STAIRS\n"
text += "BRO! I TOLD YOU DOG!\n"
text += "I TOLD YOU MAN!"
justified := rosed.Edit(text).Justify(30).String()
fmt.Println(justified)Output:
I WARNED YOU ABOUT STAIRS
BRO! I TOLD YOU DOG!
I TOLD YOU MAN!
You could combine this with wrapping to account for the lines that didn't quite end up the same length as the others:
text := "Your name is KANAYA MARYAM. You are one of the few of your "
text += "kind who can withstand the BLISTERING ALTERNIAN SUN, and "
text += "perhaps the only who enjoys the feel of its rays. As such, "
text += "you are one of the few of your kind who has taken a shining "
text += "to LANDSCAPING. You have cultivated a lush oasis."
wrappedAndJustified := rosed.Edit(text).Wrap(50).Justify(50).String()
fmt.Println(wrappedAndJustified)Output:
Your name is KANAYA MARYAM. You are one of the few
of your kind who can withstand the BLISTERING
ALTERNIAN SUN, and perhaps the only who enjoys the
feel of its rays. As such, you are one of the few
of your kind who has taken a shining to
LANDSCAPING. You have cultivated a lush oasis.
You can use this library to build a table of terms and their definitions:
aradiaDef := "Once had a fondness for ARCHEOLOGY, though now has trouble "
aradiaDef += "recalling this passion."
tavDef := "Known to be heavily arrested by FAIRY TALES AND FANTASY STORIES. "
tavDef += "Has an acute ability to COMMUNE WITH THE MANY CREATURES OF "
tavDef += "ALTERNIA."
solluxDef := "Is good at computers, and knows ALL THE CODES. All of them."
karkatDef := "Has a passion for RIDICULOUSLY TERRIBLE ROMANTIC MOVIES AND "
karkatDef += "ROMCOMS. Should really be EMBARRASSED for liking this DREADFUL "
karkatDef += "CINEMA, but for some reason is not."
defs := [][2]string{
{"Aradia", aradiaDef},
{"Tavros", tavDef},
{"Sollux", solluxDef},
{"Karkat", karkatDef},
}
const (
position = 0
width = 80
)
defTable := rosed.Edit("").InsertDefinitionsTable(position, defs, width).String()
fmt.Println(defTable)Output:
Aradia - Once had a fondness for ARCHEOLOGY, though now has trouble recalling
this passion.
Tavros - Known to be heavily arrested by FAIRY TALES AND FANTASY STORIES. Has
an acute ability to COMMUNE WITH THE MANY CREATURES OF ALTERNIA.
Sollux - Is good at computers, and knows ALL THE CODES. All of them.
Karkat - Has a passion for RIDICULOUSLY TERRIBLE ROMANTIC MOVIES AND ROMCOMS.
Should really be EMBARRASSED for liking this DREADFUL CINEMA, but
for some reason is not.
You can use this library to create two columns of text:
left := "You have a feeling it's going to be a long day."
right := "A familiar note is produced. It's the one Desolation plays to "
right += "keep its instrument in tune."
const (
position = 0
middleSpace = 2
width = 60
leftPercent = 0.33
)
columns := rosed.Edit("").
InsertTwoColumns(position, left, right, middleSpace, width, leftPercent).
String()
fmt.Println(columns)Output:
You have a feeling A familiar not is produced. It's the
it's going to be a one Desolation plays to keep its
long day. instrument in tune.
Do you like the idea of operating on lines with customizability but rosed's built-in Editor functions aren't enough for you? Use Apply to give your own function to run on each line, or ApplyParagraphs to run it on each paragraph.
For instance, to upper-case every line while giving each a line number:
text := "She went too far this time and she knows it.\n"
text += "She's got to pay.\n"
text += "Justice is long overdue.\n"
lineNumberMaker := func(idx int, line string) []string {
upperLine := strings.ToUpper(line)
line = fmt.Sprintf("LINE %d: %s", idx+1, upperLine)
return []string{
line,
}
}
customized := rosed.Edit(text).Apply(lineNumberMaker).String()
fmt.Println(customized)Output:
LINE 1: SHE WENT TOO FAR THIS TIME AND SHE KNOWS IT.
LINE 2: SHE'S GOT TO PAY.
LINE 3: JUSTICE IS LONG OVERDUE.
You can open a sub-editor on a sub-section of text in an Editor to only affect that part of the text.
What if we forgot to tab some lines of an indented paragraph that comes after a non-indented paragraph? Oh no! We better fix that, quick:
text := "Your name is NEPETA LEIJON.\n"
text += "\n"
text += "\tYou live in a CAVE that is also a HIVE.\n"
text += "But still mostly just a cave.\n" // this line should have an indent in it
text += "You like to engage in FRIENDLY ROLE PLAYING.\n" // so should this one
text += "\tBut not the DANGEROUS KIND.\n"
text += "\tIt's TOO DANGEROUS!"
corrected := rosed.Edit(text).Lines(3, 5).Indent(1).String()
fmt.Println(corrected)Output:
Your name is NEPETA LEIJON.
You live in a CAVE that is also a HIVE.
But still mostly just a cave.
You like to engage in FRIENDLY ROLE PLAYING.
But not the DANGEROUS KIND.
It's TOO DANGEROUS!
This library uses its GitHub Issues Page for coordination of work. If you'd like to assist with an open issue, feel free to drop a comment on the issue mentioning your interest. Then, fork the repo into your own copy, make the changes, then raise a PR to bring them in.
If there's a bug you'd like to report or new feature you'd like to see in this library, feel free to open a new issue for it.
This explains the refrance [sic] of the sample text, most of the samples, test cases, and example texts used in this library's documentation have been sampled from the text of Homestuck, a primarily text-based web comic with occasional animations whose topic matter ranges from data-structure jokes to an analysis of the self and how our past makes us. Sadly due to Flash dying a lot of the site is non-functional.
May it live on in our hearts and naming conventions longer than the darkness of the furthest ring. Or, you know, just generally exist in The Unofficial Homestuck Collection.