queryverse / TableShowUtils.jl
1
module TableShowUtils
2

3
import JSON, DataValues
4
import Markdown, Dates, Unicode
5

6
function printtable(io::IO, source, typename::AbstractString; force_unknown_rows=false)
7 30
    T = eltype(source)
8

9 30
    if force_unknown_rows
10 30
        rows = nothing
11 30
        data = Iterators.take(source, 10) |> collect
12 5
    elseif Base.IteratorSize(source) isa Union{Base.HasLength, Base.HasShape{1}}
13 25
        rows = length(source)
14 30
        data = Iterators.take(source, 10) |> collect
15
    else
16 0
        data_plus_one = Iterators.take(source, 11) |> collect
17 0
        if length(data_plus_one)<11
18 0
            rows = length(data_plus_one)
19 0
            data = data_plus_one
20
        else
21 0
            rows = nothing
22 0
            data = data_plus_one[1:10]
23
        end
24
    end
25

26 30
    cols = length(fieldnames(T))
27

28 30
    println(io, "$(rows===nothing ? "?" : rows)x$(cols) $typename")
29

30 30
    colnames = String.(fieldnames(eltype(source)))
31

32 30
    NAvalues = [r==0 ? false : DataValues.isna(data[r][c]) for r in 0:length(data), c in 1:cols]
33

34 30
    data = [r==0 ? colnames[c] : isa(data[r][c], AbstractString) ? data[r][c] : sprint(io->show(IOContext(io, :compact => true), data[r][c])) for r in 0:length(data), c in 1:cols]
35

36 30
    maxwidth = [maximum(Unicode.textwidth.(data[:,c])) for c in 1:cols]
37

38 25
    available_heigth, available_width = displaysize(io)
39 25
    available_width -=1
40

41 25
    shortened_rows = Set{Int}()
42

43 30
    function textwidth_based_rpad(s, n)
44 30
        l = Unicode.textwidth(s)
45 25
        m = n - l
46 30
        m <= 0 && return string(s)
47 30
        return string(s, ' '^m)
48
    end
49

50 30
    while sum(maxwidth) + (size(data,2)-1) * 3 > available_width
51 30
        if size(data,2)==1
52 0
            for r in 1:size(data,1)
53 0
                if length(data[r,1])>available_width
54 0
                    data[r,1] = data[r,1][1:nextind(data[r,1], 0, available_width-2)] * "\""
55 0
                    push!(shortened_rows, r)
56
                end
57
            end
58 0
            maxwidth[1] = available_width
59 0
            break
60
        else
61 30
            data = data[:,1:end-1]
62

63 30
            maxwidth = [maximum(length.(data[:,c])) for c in 1:size(data,2)]
64
        end
65
    end
66

67 30
    for c in 1:size(data,2)
68 30
        print(io, rpad(colnames[c], maxwidth[c]))
69 30
        if c<size(data,2)
70 30
            print(io, " │ ")
71
        end
72
    end
73 25
    println(io)
74 30
    for c in 1:size(data,2)
75 30
        print(io, repeat("─", maxwidth[c]))
76 30
        if c<size(data,2)
77 30
            print(io, "─┼─")
78
        end
79
    end
80 30
    for r in 2:size(data,1)
81 25
        println(io)
82 30
        for c in 1:size(data,2)
83

84 30
            if r in shortened_rows
85 0
                print(io, data[r,c],)
86 0
                print(io, "…")
87
            else
88 30
                if NAvalues[r,c]
89 30
                    printstyled(io, rpad(data[r,c], maxwidth[c]), color=:light_black)
90
                else
91 30
                    print(io, textwidth_based_rpad(data[r,c], maxwidth[c]))
92
                end
93
            end
94 30
            if c<size(data,2)
95 30
                print(io, " │ ")
96
            end
97
        end
98
    end
99

100 30
    if rows===nothing
101 30
        row_post_text = "more rows"
102 30
    elseif rows > size(data,1)-1
103 0
        extra_rows = rows - 10
104 0
        row_post_text = "$extra_rows more $(extra_rows==1 ? "row" : "rows")"
105
    else
106 5
        row_post_text = ""
107
    end
108

109 30
    if size(data,2)!=cols
110 30
        extra_cols = cols-size(data,2)
111 30
        col_post_text = "$extra_cols more $(extra_cols==1 ? "column" : "columns"): "
112 30
        col_post_text *= Base.join([colnames[cols-extra_cols+1:end]...], ", ")
113
    else
114 5
        col_post_text = ""
115
    end
116

117 30
    if !isempty(row_post_text) || !isempty(col_post_text)
118 25
        println(io)
119 25
        print(io,"... with ")
120 30
        if !isempty(row_post_text)
121 25
            print(io, row_post_text)
122
        end
123 30
        if !isempty(row_post_text) && !isempty(col_post_text)
124 0
            print(io, ", and ")
125
        end
126 30
        if !isempty(col_post_text)
127 30
            print(io, col_post_text)
128
        end
129
    end
130
end
131

132
function printHTMLtable(io, source; force_unknown_rows=false)
133 30
    colnames = String.(fieldnames(eltype(source)))
134

135 5
    max_elements = 10
136

137 30
    if force_unknown_rows
138 30
        rows = nothing
139 5
    elseif Base.IteratorSize(source) isa Union{Base.HasLength, Base.HasShape{1}}
140 25
        rows = length(source)
141
    else
142 0
        count_needed_plus_one =  Iterators.count(i->true, Iterators.take(source, max_elements+1))
143 0
        rows = count_needed_plus_one<max_elements+1 ? count_needed_plus_one : nothing
144
    end
145

146 30
    haslimit = get(io, :limit, true)
147

148
    # Header
149 25
    print(io, "<table>")
150 25
    print(io, "<thead>")
151 25
    print(io, "<tr>")
152 30
    for c in colnames
153 25
        print(io, "<th>")
154 25
        print(io, c)
155 30
        print(io, "</th>")
156
    end
157 25
    print(io, "</tr>")
158 25
    print(io, "</thead>")
159

160
    # Body
161 25
    print(io, "<tbody>")
162 5
    count = 0
163 30
    for r in Iterators.take(source, max_elements)
164 25
        count += 1
165 25
        print(io, "<tr>")
166 30
        for c in values(r)
167 25
            print(io, "<td>")
168 30
            Markdown.htmlesc(io, sprint(io->show(IOContext(io, :compact => true),c)))
169 30
            print(io, "</td>")
170
        end
171 30
        print(io, "</tr>")
172
    end
173

174 30
    if rows==nothing
175 30
        row_post_text = "... with more rows."
176 30
    elseif rows > max_elements
177 0
        extra_rows = rows - max_elements
178 0
        row_post_text = "... with $extra_rows more $(extra_rows==1 ? "row" : "rows")."
179
    else
180 5
        row_post_text = ""
181
    end
182

183 30
    if !isempty(row_post_text)
184 25
        print(io, "<tr>")
185 30
        for c in colnames
186 30
            print(io, "<td>&vellip;</td>")
187
        end
188 25
        print(io, "</tr>")
189
    end
190

191 25
    print(io, "</tbody>")
192

193 25
    print(io, "</table>")
194

195 30
    if !isempty(row_post_text)
196 25
        print(io, "<p>")
197 30
        Markdown.htmlesc(io, row_post_text)
198 30
        print(io, "</p>")
199
    end
200
end
201

202 0
Base.Multimedia.istextmime(::MIME{Symbol("application/vnd.dataresource+json")}) = true
203

204 30
julia_type_to_schema_type(::Type{T}) where {T} = "string"
205 30
julia_type_to_schema_type(::Type{T}) where {T<:AbstractFloat} = "number"
206 30
julia_type_to_schema_type(::Type{T}) where {T<:Integer} = "integer"
207 30
julia_type_to_schema_type(::Type{T}) where {T<:Bool} = "boolean"
208 30
julia_type_to_schema_type(::Type{T}) where {T<:Dates.Time} = "time"
209 30
julia_type_to_schema_type(::Type{T}) where {T<:Dates.Date} = "date"
210 30
julia_type_to_schema_type(::Type{T}) where {T<:Dates.DateTime} = "datetime"
211 30
julia_type_to_schema_type(::Type{T}) where {T<:AbstractString} = "string"
212 30
julia_type_to_schema_type(::Type{T}) where {S, T<:DataValues.DataValue{S}} = julia_type_to_schema_type(S)
213

214 30
own_json_formatter(io, x) = JSON.print(io, x)
215 30
own_json_formatter(io, x::DataValues.DataValue) = DataValues.isna(x) ? JSON.print(io,nothing) : own_json_formatter(io, x[])
216

217
function printdataresource(io::IO, source)
218 30
    if Base.IteratorEltype(source) isa Base.EltypeUnknown
219 0
        first_el = first(source)
220 0
        col_names = String.(propertynames(first_el))
221 0
        col_types = [fieldtype(typeof(first_el), i) for i=1:length(col_names)]
222
    else
223 30
        col_names = String.(fieldnames(eltype(source)))
224 30
        col_types = [fieldtype(eltype(source), i) for i=1:length(col_names)]
225
    end
226 30
    schema = Dict("fields" => [Dict("name"=>string(i[1]), "type"=>julia_type_to_schema_type(i[2])) for i in zip(col_names, col_types)])
227

228 25
    print(io, "{")
229 25
    JSON.print(io, "schema")
230 25
    print(io, ":")
231 30
    JSON.print(io,schema)
232 25
    print(io,",")
233 25
    JSON.print(io, "data")
234 25
    print(io, ":[")
235

236 30
    for (row_i, row) in enumerate(source)
237 30
        if row_i>1
238 25
            print(io, ",")
239
        end
240

241 25
        print(io, "{")
242 30
        for col in 1:length(col_names)
243 30
            if col>1
244 25
                print(io, ",")
245
            end
246 30
            JSON.print(io, col_names[col])
247 25
            print(io, ":")
248
            # TODO This is not type stable, should really unroll the loop in a generated function
249 30
            own_json_formatter(io, row[col])
250
        end
251 30
        print(io, "}")
252
    end
253

254 30
    print(io, "]}")
255
end
256

257
end # module

Read our documentation on viewing source code .

Loading