Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ julia = "1.10"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Dates", "DelimitedFiles", "JET", "OffsetArrays", "StableRNGs", "Test"]
test = ["Dates", "DelimitedFiles", "JET", "Markdown", "OffsetArrays", "StableRNGs", "Test"]
14 changes: 12 additions & 2 deletions src/statmodels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,18 @@ function show(io::IO, ::MIME"text/plain", ct::CoefTable)
nothing
end

# An unescaped `|` ends the cell it appears in, so a row containing one no longer
# has the same number of cells as the alignment row and the table stops parsing as
# a table at all. Callers hit this with the R-style `Pr(>|z|)` p-value header, and
# with any string cell or row name that happens to contain a pipe. Same rule the
# Markdown stdlib applies in `plain(::IO, ::Markdown.Table)`; applying it before
# the column widths are measured keeps them consistent with what is printed.
escape_markdown_pipes(s::AbstractString) = replace(s, '|' => "\\|")

function show(io::IO, ::MIME"text/markdown", ct::CoefTable)
cols = ct.cols; rownms = ct.rownms; colnms = ct.colnms;
cols = ct.cols
rownms = escape_markdown_pipes.(ct.rownms)
colnms = escape_markdown_pipes.(ct.colnms)
nc = length(cols)
nr = length(cols[1])
if length(rownms) == 0
Expand All @@ -157,7 +167,7 @@ function show(io::IO, ::MIME"text/markdown", ct::CoefTable)
mat = [j == 1 ? NoQuote(rownms[i]) :
j-1 == ct.pvalcol ? NoQuote(sprint(show, PValue(cols[j-1][i]))) :
j-1 in ct.teststatcol ? TestStat(cols[j-1][i]) :
cols[j-1][i] isa AbstractString ? NoQuote(cols[j-1][i]) : cols[j-1][i]
cols[j-1][i] isa AbstractString ? NoQuote(escape_markdown_pipes(cols[j-1][i])) : cols[j-1][i]
for i in 1:nr, j in 1:nc+1]
# Code inspired by print_matrix in Base
io = IOContext(io, :compact=>true, :limit=>false)
Expand Down
20 changes: 19 additions & 1 deletion test/statmodels.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using StatsBase
using StatsBase: PValue, TestStat
using Test, Random, StatsAPI, LinearAlgebra
using Test, Random, StatsAPI, LinearAlgebra, Markdown

v1 = [1.45666, -23.14, 1.56734e-13]
v2 = ["Good", "Great", "Bad"]
Expand Down Expand Up @@ -46,6 +46,24 @@ x3 1.56734e-13 Bad 2 0.00 <1e-15
| [2] | -23.14 | Great | 56 | 0.13 | 0.3467 |
| [3] | 1.56734e-13 | Bad | 2 | 0.00 | <1e-15 |"""

# Pipes in a header, a row name or a string cell must be escaped: an unescaped one
# ends its cell, so the row presents more cells than the alignment row and the
# whole thing stops parsing as a table. Downstream packages hit this with the
# R-style `Pr(>|z|)` p-value header.
ct_pipes = CoefTable(Any[v1, ["a|b", "Great", "Bad"], v3, v4, v5],
["Estimate", "Comments", "df", "t", "Pr(>|z|)"],
["x|1", "x2", "x3"], 5, 4)
md_pipes = sprint(show, MIME"text/markdown"(), ct_pipes)
@test occursin(raw"Pr(>\|z\|)", md_pipes)
@test occursin(raw"x\|1", md_pipes)
@test occursin(raw"a\|b", md_pipes)
# Every row must present as many cells as the alignment row.
let counts = [length(collect(eachmatch(r"(?<!\\)\|", row))) for row in split(md_pipes, '\n')]
@test all(==(first(counts)), counts)
end
# ... and the result must parse as a table rather than degrade to a paragraph.
@test only(Markdown.parse(md_pipes).content) isa Markdown.Table

@test length(ct) === 3
@test eltype(ct) ==
NamedTuple{(:Name, :Estimate, :Comments, :df, :t, :p),
Expand Down