From 39841562926829f06f09e8735b15e9a110e46aad Mon Sep 17 00:00:00 2001 From: TEC Date: Mon, 1 Apr 2024 15:08:57 +0800 Subject: [PATCH] Docs: create an examples page --- docs/make.jl | 1 + docs/src/examples.md | 176 +++++++++++++++++++++++++++++++++++++++++++ docs/src/index.md | 2 +- 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 docs/src/examples.md diff --git a/docs/make.jl b/docs/make.jl index 8f4c0d30..8246d9fd 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -11,6 +11,7 @@ makedocs(; format = Documenter.HTML(ansicolor = true), pages = [ "StyledStrings" => "index.md", + "Example Usage" => "examples.md", "Internals" => "internals.md", ], warnonly = [:cross_references], diff --git a/docs/src/examples.md b/docs/src/examples.md new file mode 100644 index 00000000..ab254b46 --- /dev/null +++ b/docs/src/examples.md @@ -0,0 +1,176 @@ +# Usage Examples + +!!! info "Visual inconsistencies" + There are some examples on this page that don't quite look as they should, see [Documenter.jl#2488](https://github.com/JuliaDocs/Documenter.jl/issues/2488) for more information and potential improvements to this situation. + +With flexibility and composability as major considerations in the design of +StyledStrings, it is easy to describe the capabilities of this system while +failing to actually convey what it can accomplish. With this in mind, examples +can be particularly useful to show how it can be used. + +A styled string can be constructed manually, but the [`styled"..."`](@ref +@styled_str) literal is almost always a nicer option. We can see what a manual construction would involve by extracting the string and annotation parts of a [`AnnotatedString`](@ref Base.AnnotatedString). + +```@repl examples +using StyledStrings +str = styled"{yellow:hello} {blue:there}" +(String(str), Base.annotations(str)) +``` + +```@setup example +# Due to a bug in Documenter.jl we've run into issues with printing +# with color. This should always happen here, so we can try a hacky +# workaround for now. +Base.get(::IOContext, s::Symbol, d::Bool) = s === :color || d +``` + +Most of the examples here will show [`AnnotatedString`](@ref Base.AnnotatedString) in a terminal/REPL context, however they can be trivially adapted to produce HTML with `show(::IO, ::MIME"text/plain", ::AnnotatedString)`, and other packages wanting do deal with styled content in other contexts will likely find integration with StyledStrings worthwhile. + +As an end-user or a package-author, adding color is one of if not the most frequent application of styling. The default set of colors has the eight ANSI colors, and if we're to be brutally honest, `black`/`white` are just shades, leaving us with: + +```@repl examples +styled"{red:■} {green:■} {yellow:■} {blue:■} {magenta:■} {cyan:■}" +``` + +along with "bright" variants + +```@repl examples +styled"{bright_red:■} {bright_green:■} {bright_yellow:■} \ + {bright_blue:■} {bright_magenta:■} {bright_cyan:■}" +``` + +This seems somewhat limited, because it is. This is only the _default_ set of +colors though. It is important to note that the way the color `red` is +implemented is by having it name a `Face(foreground=:red)` value. The ANSI +printer knows to handle the ANSI named colors specially, but you can create +more "named colors" simply by adding new faces. + +```@repl examples +StyledStrings.addface!(:orange => StyledStrings.Face(foreground = 0xFF7700)) +styled"{orange:this is orange text}" +``` + +!!! warning "Appropriate face naming" + The face name `orange` is used here as an example, but this would be + inappropriate for a package to introduce as it's missing the `packagename_` + prefix. This is important for predictability, and to prevent name clashes. + +The fact that named colors are implemented this way also allows for other nice +conveniences. For example, if you wanted a more subtle version of the warning +styling, you could print text with the underline color set to the warning +foreground color. + +```@repl examples +styled"{(underline=warning):this is some minor/slight warning text}" +styled"A very major {(fg=error,bg=warning),bold:warning!}" +``` + +Should you want to use a particular color just once, the `foreground`/`fg` and `background`/`bg` inline face attributes can be set to hex codes. + +```@repl examples +styled"{(fg=#4063d8):ju}{(fg=#389826):l}{(fg=#cb3c33):i}{(fg=#9558b2):a}" +``` + +It is recommended that package authors create faces with a focus on the semantic +meaning they wish to impart, and then consider what styling suits. For example, +say that a hypothetical package `foobar` wants to mark something as important. +Creating a named face allows for it to be re-used across the codebase and allows +the styling everywhere its used to be updated by only changing the line +declaring it. `foobar_important` would be an appropriate name for such a face. + +```@repl examples +StyledStrings.addface!(:foobar_important => StyledStrings.Face(weight = :bold, inherit = :emphasis)) +styled"this is some {foobar_important:rather important} content" +``` + +Other packages that interact with `foobar` can also re-use the +`foobar_important` face for consistent styling. This is possible even for +packages that don't have `foobar` as a direct dependency, as faces that don't +exist are just ignored. Consider this styled content as an example: + +```@repl examples +styled"{info,foobar_important,baz_important:some text}" +``` + +The styling of `"some text"` will be based only on `info` if neither +`foobar_important` or `baz_important` are defined. Since `foobar_important` _is_ +defined, after applying the attributes of `info`, the attributes of +`foobar_important` are applied to `"some text"` _overwriting_ any attributes set +by `info`. Should `bar_important` be defined in the future, any attributes it +sets will override `foobar_important` and `info`. Put more simply, the last face +mentioned "wins". + +!!! note + The silent ignoring of undefined faces is important in making it so that it's known if a [`styled"..."`](@ref @styled_str) string will cause errors when printed is known at compile-time instead of runtime. + +Naming faces also allows for convenient customisation. Once `foobar_important` +is defined, a user can change how it is styled in their `faces.toml`. + +```toml +[foobar.important] +italic = true +``` + +!!! note "Accessibility" + User-customisation is particularly important when using color, as it allows people with color-blindness or other neuro-ophthalmological abnormalities to make text easier to read/distinguish. + +Named faces can also be customised on-the-fly in certain printing contexts created by [`withfaces`](@ref StyledStrings.withfaces). + +```@repl examples; ansicolor=true +StyledStrings.withfaces(:foobar_important => :tip) do + println(styled"Sometimes you might want {foobar_important:some text} to look different") +end +StyledStrings.withfaces(:log_info => [:magenta, :italic]) do + @info "Hello there" +end +``` + +This feature can be used to for example change the default colors to follow a +certain color theme when generating HTML output. + +```@repl examples +StyledStrings.withfaces(:red => StyledStrings.Face(foreground = 0xCF866F), + :yellow => StyledStrings.Face(foreground = 0xECBD7A), + :magenta => StyledStrings.Face(foreground = 0xB38DAC)) do + str = styled"Sometimes you might want {red:different} {yellow:shades} of {magenta:colors}." + println(str, "\n") + show(stdout, MIME("text/html"), str) +end +``` + +As you work with more styled content, the ability to compose styled content and +styling information is rather useful. Helpfully, StyledStrings allows for +interpolation of both content and attributes. + +```@repl examples +small_rainbow = (:red, :yellow, :green, :blue, :magenta) +color = join([styled"{$f:$c}" for (f, c) in tuple.(small_rainbow, collect("color"))]) +styled"It's nice to include $color, and it composes too: {bold,inverse:$color}" +``` + +Sometimes it's useful to compose a string incrementally, or interoperate with +other `IO`-based code. For these use-cases, the [`AnnotatedIOBuffer`](@ref Base.AnnotatedIOBuffer) is very handy, as you can [`read`](@ref Base.read) an [`AnnotatedString`](@ref Base.AnnotatedString) from it. + +```@repl examples +aio = Base.AnnotatedIOBuffer() +typ = Int +print(aio, typ) +while typ != Any # We'll pretend that `supertypes` doesn't exist. + typ = supertype(typ) + print(aio, styled" {bright_red:<:} $typ") +end +read(seekstart(aio), Base.AnnotatedString) +``` + +StyledStrings adds a specialised [`printstyled`](@ref) method `printstyled(::AnnotatedIOBuffer, ...)` that means that you can pass an `AnnotatedIOBuffer` as IO to "legacy" code written to use `printstyled`, and extract all the styling as though it had used [`styled"..."`](@ref @styled_str) macros. + +```@repl +aio = Base.AnnotatedIOBuffer() +printstyled(aio, 'c', color=:red) +printstyled(aio, 'o', color=:yellow) +printstyled(aio, 'l', color=:green) +printstyled(aio, 'o', color=:blue) +printstyled(aio, 'r', color=:magenta) +read(seekstart(aio), Base.AnnotatedString) +read(seekstart(aio), String) +``` diff --git a/docs/src/index.md b/docs/src/index.md index 3414eca4..9454e5ec 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -70,7 +70,7 @@ referred to simply by name. Packages can add faces to this dictionary via the [`addface!`](@ref StyledStrings.addface!) function, and the loaded faces can be easily [customised](@ref stdlib-styledstrings-face-toml). -!!! warning +!!! warning "Appropriate face naming" Any package registering new faces should ensure that they are prefixed by the package name, i.e. follow the format `mypackage_myface`. This is important for predictability, and to prevent name clashes.