Skip to content

Commit

Permalink
Make compatible with Julia 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
tecosaur committed May 2, 2024
1 parent d7496d2 commit 4a25c17
Show file tree
Hide file tree
Showing 21 changed files with 2,442 additions and 187 deletions.
18 changes: 13 additions & 5 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ on:
push:
branches:
- main
- julia1-compat
tags: ['*']
pull_request:
concurrency:
Expand All @@ -18,10 +19,17 @@ jobs:
fail-fast: false
matrix:
version:
# - '1.0'
# - '1.6'
# - '1'
- 'nightly'
- '1.0'
- '1.1'
- '1.2'
- '1.3'
- '1.4'
- '1.5'
- '1.6'
- '1.7'
- '1.8'
- '1.9'
- '1.10'
os:
- ubuntu-latest
- macOS-latest
Expand Down Expand Up @@ -53,7 +61,7 @@ jobs:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
with:
version: 'nightly'
version: '1.10'
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
Expand Down
12 changes: 8 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
name = "StyledStrings"
uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
authors = ["TEC <git@tecosaur.net>"]
version = "1.11.0"
version = "1.0.0"

[deps]
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"

[compat]
julia = "1.11"
# 1.0 - 1.10, but the range version isn't supported till 1.4
# In 1.11+ precompilation fails due to method overwriting.
julia = "1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10"

[extras]
Supposition = "5a0628fe-1738-4658-9b6d-0b7605a9755b"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Supposition", "Test"]
test = ["Test"]
2 changes: 1 addition & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ makedocs(;
"Example Usage" => "examples.md",
"Internals" => "internals.md",
],
warnonly = [:cross_references],
warnonly = [:cross_references, :missing_docs],
)

deploydocs(repo="github.com/JuliaLang/StyledStrings.jl",
Expand Down
12 changes: 6 additions & 6 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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).
@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 StyledStrings.AnnotatedString).

```@repl examples
using StyledStrings
Expand Down Expand Up @@ -172,28 +172,28 @@ 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.
other `IO`-based code. For these use-cases, the [`AnnotatedIOBuffer`](@ref StyledStrings.AnnotatedIOBuffer) is very handy, as you can [`read`](@ref Base.read) an [`AnnotatedString`](@ref StyledStrings.AnnotatedString) from it.

```@repl examples
aio = Base.AnnotatedIOBuffer()
aio = StyledStrings.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)
read(seekstart(aio), StyledStrings.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()
aio = StyledStrings.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), StyledStrings.AnnotatedString)
read(seekstart(aio), String)
```
12 changes: 6 additions & 6 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ output formats.

Instead of leaving this headache to be widely experienced downstream, it is
tackled head-on by the introduction of a special string type
([`AnnotatedString`](@ref Base.AnnotatedString)). This string type wraps any other
([`AnnotatedString`](@ref StyledStrings.AnnotatedString)). This string type wraps any other
[`AbstractString`](@ref) type and allows for formatting information to be applied to regions (e.g.
characters 1 through to 7 are bold and red).

Expand All @@ -29,15 +29,15 @@ convenience, faces in the global faces dictionary (e.g. `shadow`) can just be
named instead of giving the [`Face`](@ref StyledStrings.Face) directly.

Along with these capabilities, we also provide a convenient way for constructing
[`AnnotatedString`](@ref Base.AnnotatedString)s, detailed in [Styled String
[`AnnotatedString`](@ref StyledStrings.AnnotatedString)s, detailed in [Styled String
Literals](@ref stdlib-styledstring-literals).

```@repl demo
using StyledStrings
styled"{yellow:hello} {blue:there}"
```

## Styling via [`AnnotatedString`](@ref Base.AnnotatedString)s
## Styling via [`AnnotatedString`](@ref StyledStrings.AnnotatedString)s

## [Faces](@id stdlib-styledstrings-faces)

Expand Down Expand Up @@ -129,7 +129,7 @@ On initialization, the `config/faces.toml` file under the first Julia depot (usu
### Applying faces to a `AnnotatedString`

By convention, the `:face` attributes of a [`AnnotatedString`](@ref
Base.AnnotatedString) hold information on the [`Face`](@ref StyledStrings.Face)s
StyledStrings.AnnotatedString) hold information on the [`Face`](@ref StyledStrings.Face)s
that currently apply. This can be given in multiple forms, as a single `Symbol`
naming a [`Face`](@ref StyledStrings.Face)s in the global face dictionary, a
[`Face`](@ref StyledStrings.Face) itself, or a vector of either.
Expand All @@ -143,7 +143,7 @@ them to the properties list afterwards, or use the convenient [Styled String
literals](@ref stdlib-styledstring-literals).

```@repl demo
str1 = Base.AnnotatedString("blue text", [(1:9, :face => :blue)])
str1 = StyledStrings.AnnotatedString("blue text", [(1:9, :face => :blue)])
str2 = styled"{blue:blue text}"
str1 == str2
sprint(print, str1, context = :color => true)
Expand All @@ -152,7 +152,7 @@ sprint(show, MIME("text/html"), str1, context = :color => true)

## [Styled String Literals](@id stdlib-styledstring-literals)

To ease construction of [`AnnotatedString`](@ref Base.AnnotatedString)s with [`Face`](@ref StyledStrings.Face)s applied,
To ease construction of [`AnnotatedString`](@ref StyledStrings.AnnotatedString)s with [`Face`](@ref StyledStrings.Face)s applied,
the [`styled"..."`](@ref @styled_str) styled string literal allows for the content and
attributes to be easily expressed together via a custom grammar.

Expand Down
20 changes: 15 additions & 5 deletions src/StyledStrings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

module StyledStrings

import Base: AnnotatedString, AnnotatedChar, annotations, annotate!,
annotatedstring, convert, merge, show, print, write,
ScopedValue, with, @with
import Base: convert, merge, show, print, write

export @styled_str
public Face, addface!, withfaces, styled, SimpleColor

include("compat.jl")
using .Compat

const ncodeunits = Compat.ncodeunits

include("strings/strings.jl")
include("terminfo.jl")

import .AnnotatedStrings: AnnotatedString, AnnotatedChar, annotations,
annotate!, annotatedstring, annotatedstring_optimize!, AnnotatedIOBuffer

include("faces.jl")
include("regioniterator.jl")
Expand All @@ -18,12 +26,14 @@ include("legacy.jl")
using .StyledMarkup

function __init__()
term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb")
global current_terminfo = load_terminfo(term_env)
userfaces = joinpath(first(DEPOT_PATH), "config", "faces.toml")
isfile(userfaces) && loaduserfaces!(userfaces)
Legacy.load_env_colors!()
end

if Base.generating_output()
if generating_output()
include("precompile.jl")
end

Expand Down
176 changes: 176 additions & 0 deletions src/compat.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

module Compat

export isnothing, _str_sizehint, takewhile,
escape_raw_string, peek, parseatom, generating_output

@static if VERSION < v"1.1"
isnothing(x) = x === nothing
end

@static if VERSION < v"1.2"
ncodeunits(s::AbstractString) = Base.ncodeunits(s)
ncodeunits(c::AbstractChar) = Base.ncodeunits(string(c))
else
import Base.ncodeunits
end

@static if VERSION < v"1.3"
function _str_sizehint(x)
if x isa Float64
return 20
elseif x isa Float32
return 12
elseif x isa String || x isa SubString{String}
return sizeof(x)
elseif x isa Char
return ncodeunits(x)
else
return 8
end
end
else
import Base._str_sizehint
end

@static if VERSION < v"1.3"
function unescape_string(io::IO, s::AbstractString, keep = ())
a = Iterators.Stateful(s)
for c in a
if !isempty(a) && c == '\\'
c = popfirst!(a)
if c in keep
print(io, '\\', c)
elseif c == 'x' || c == 'u' || c == 'U'
n = k = 0
m = c == 'x' ? 2 :
c == 'u' ? 4 : 8
while (k += 1) <= m && !isempty(a)
nc = peek(a)
n = '0' <= nc <= '9' ? n<<4 + (nc-'0') :
'a' <= nc <= 'f' ? n<<4 + (nc-'a'+10) :
'A' <= nc <= 'F' ? n<<4 + (nc-'A'+10) : break
popfirst!(a)
end
if k == 1 || n > 0x10ffff
u = m == 4 ? 'u' : 'U'
throw(ArgumentError("invalid $(m == 2 ? "hex (\\x)" :
"unicode (\\$u)") escape sequence"))
end
if m == 2 # \x escape sequence
write(io, UInt8(n))
else
print(io, Char(n))
end
elseif '0' <= c <= '7'
k = 1
n = c-'0'
while (k += 1) <= 3 && !isempty(a)
c = peek(a)
n = ('0' <= c <= '7') ? n<<3 + c-'0' : break
popfirst!(a)
end
if n > 255
throw(ArgumentError("octal escape sequence out of range"))
end
write(io, UInt8(n))
else
print(io, c == 'a' ? '\a' :
c == 'b' ? '\b' :
c == 't' ? '\t' :
c == 'n' ? '\n' :
c == 'v' ? '\v' :
c == 'f' ? '\f' :
c == 'r' ? '\r' :
c == 'e' ? '\e' :
(c == '\\' || c == '"') ? c :
throw(ArgumentError("invalid escape sequence \\$c")))
end
else
print(io, c)
end
end
end
unescape_string(s::AbstractString, keep = ()) =
sprint(unescape_string, s, keep; sizehint=lastindex(s))
end

@static if VERSION < v"1.4"
function takewhile(f::Function, itr)
taken = Vector{eltype(itr)}()
next = iterate(itr)
while !isnothing(next) && ((item, state) = next) |> first |> f
push!(taken, item)
next = iterate(itr, state)
end
taken
end
function escape_raw_string(io::IO, str::AbstractString, delim::Char='"')
total = 0
escapes = 0
for c in str
if c == '\\'
escapes += 1
else
if c == delim
# if one or more backslashes are followed by
# a double quote then escape all backslashes
# and the double quote
escapes += 1
total += escapes
while escapes > 0
write(io, '\\')
escapes -= 1
end
end
escapes = 0
end
write(io, c)
end
# also escape any trailing backslashes,
# so they do not affect the closing quote
total += escapes
while escapes > 0
write(io, '\\')
escapes -= 1
end
total
end
function escape_raw_string(str::AbstractString, delim::Char='"')
total = escape_raw_string(devnull, str, delim) # check whether the string even needs to be copied and how much to allocate for it
return total == 0 ? str : sprint(escape_raw_string, str, delim; sizehint = sizeof(str) + total)
end
else
const takewhile = Iterators.takewhile
const escape_raw_string = Base.escape_raw_string
end

@static if VERSION < v"1.5"
function peek(s::Iterators.Stateful, sentinel=nothing)
ns = s.nextvalstate
return ns !== nothing ? ns[1] : sentinel
end
end

@static if VERSION < v"1.6"
function parseatom(text::AbstractString, pos::Integer)
Meta.parse(text, pos, greedy = false)
end
else
const parseatom = Meta.parseatom
end

@static if VERSION < v"1.11"
function generating_output(incremental::Union{Bool,Nothing}=nothing)
ccall(:jl_generating_output, Cint, ()) == 0 && return false
if incremental !== nothing
Base.JLOptions().incremental == incremental || return false
end
return true
end
else
const generating_output = Base.generating_output
end

end
Loading

0 comments on commit 4a25c17

Please sign in to comment.