• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37997

29 Jan 2025 02:08AM UTC coverage: 17.283% (-68.7%) from 85.981%
#37997

push

local

web-flow
bpart: Start enforcing min_world for global variable definitions (#57150)

This is the analog of #57102 for global variables. Unlike for consants,
there is no automatic global backdate mechanism. The reasoning for this
is that global variables can be declared at any time, unlike constants
which can only be decalared once their value is available. As a result
code patterns using `Core.eval` to declare globals are rarer and likely
incorrect.

1 of 22 new or added lines in 3 files covered. (4.55%)

31430 existing lines in 188 files now uncovered.

7903 of 45728 relevant lines covered (17.28%)

98663.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.31
/stdlib/Printf/src/Printf.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2
"""
3
The `Printf` module provides formatted output functions similar to the C standard library's `printf`. It allows formatted printing to an output stream or to a string.
4
"""
5
module Printf
6

7
using Base.Ryu
8

9
export @printf, @sprintf
10

11
public format, Format
12

13
# format specifier categories
14
const Ints = Union{Val{'d'}, Val{'i'}, Val{'u'}, Val{'x'}, Val{'X'}, Val{'o'}}
15
const Floats = Union{Val{'e'}, Val{'E'}, Val{'f'}, Val{'F'}, Val{'g'}, Val{'G'}, Val{'a'}, Val{'A'}}
16
const Chars = Union{Val{'c'}, Val{'C'}}
17
const Strings = Union{Val{'s'}, Val{'S'}}
18
const Pointer = Val{'p'}
19
const HexBases = Union{Val{'x'}, Val{'X'}, Val{'a'}, Val{'A'}}
20
const PositionCounter = Val{'n'}
21

22
const MAX_FRACTIONAL_PART_WIDTH = 17  # max significant decimals + 1: `ceil(Int, log10(1 / eps(Float64))) + 1`
23
const MAX_INTEGER_PART_WIDTH = 309  # max exponent: `ceil(Int, log10(prevfloat(typemax(Float64))))`
24
const MAX_FMT_CHARS_WIDTH = 5  # hash | sign +/- | decimal dot | exponent e/E | exponent sign
25

26
"""
27
Typed representation of a format specifier.
28

29
`T` is a `Val{'_'}`, where `_` is a valid format specifier character.
30

31
Fields are the various modifiers allowed for various format specifiers.
32
"""
33
struct Spec{T} # T => %type => Val{'type'}
34
    leftalign::Bool
5✔
35
    plus::Bool
36
    space::Bool
37
    zero::Bool
38
    hash::Bool
39
    width::Int
40
    precision::Int
41
    dynamic_width::Bool
42
    dynamic_precision::Bool
43
end
44

45
# recreate the format specifier string from a typed Spec
UNCOV
46
Base.string(f::Spec{T}; modifier::String="") where {T} =
×
47
    string("%",
48
           f.leftalign ? "-" : "",
49
           f.plus ? "+" : "",
50
           f.space ? " " : "",
51
           f.zero ? "0" : "",
52
           f.hash ? "#" : "",
53
           f.dynamic_width ? "*" : (f.width > 0 ? f.width : ""),
54
           f.dynamic_precision ? ".*" : (f.precision == 0 ? ".0" : (f.precision > 0 ? ".$(f.precision)" : "")),
55
           modifier,
56
           char(T))
57

58
Base.show(io::IO, f::Spec) = print(io, string(f))
×
59

UNCOV
60
floatfmt(s::Spec{T}) where {T} =
×
61
    Spec{Val{'f'}}(s.leftalign, s.plus, s.space, s.zero, s.hash, s.width, 0, s.dynamic_width, s.dynamic_precision)
UNCOV
62
ptrfmt(s::Spec{T}, x) where {T} =
×
63
    Spec{Val{'x'}}(s.leftalign, s.plus, s.space, s.zero, true, s.width, sizeof(x) == 8 ? 16 : 8, s.dynamic_width, s.dynamic_precision)
64

65
"""
66
    Printf.Format(format_str)
67

68
Create a C printf-compatible format object that can be used for formatting values.
69

70
The input `format_str` can include any valid format specifier character and modifiers.
71

72
A `Format` object can be passed to `Printf.format(f::Format, args...)` to produce a
73
formatted string, or `Printf.format(io::IO, f::Format, args...)` to print the
74
formatted string directly to `io`.
75

76
For convenience, the `Printf.format"..."` string macro form can be used for building
77
a `Printf.Format` object at macro-expansion-time.
78

79
!!! compat "Julia 1.6"
80
    `Printf.Format` requires Julia 1.6 or later.
81
"""
82
struct Format{S, T}
83
    str::S # original full format string as CodeUnits
5✔
84
    # keep track of non-format specifier strings to print
85
    # length(substringranges) == length(formats) + 1
86
    # so when printing, we start with printing
87
      # str[substringranges[1]], then formats[1] + args[1]
88
      # then str[substringranges[2]], then formats[2] + args[2]
89
      # and so on, then at the end, str[substringranges[end]]
90
    substringranges::Vector{UnitRange{Int}}
91
    formats::T # Tuple of Specs
92
    numarguments::Int  # required for dynamic format specifiers
93
end
94

95
# what number base should be used for a given format specifier?
UNCOV
96
base(T) = T <: HexBases ? 16 : T <: Val{'o'} ? 8 : 10
×
UNCOV
97
char(::Type{Val{c}}) where {c} = c
×
98

99
struct InvalidFormatStringError <: Exception
UNCOV
100
    message::String
×
101
    format::String
102
    start_color::Int
103
    end_color::Int
104
end
105

106
function Base.showerror(io::IO, err::InvalidFormatStringError)
×
107
    io_has_color = get(io, :color, false)::Bool
×
108

109
    println(io, "InvalidFormatStringError: ", err.message)
×
110
    print(io, "    \"", @view(err.format[begin:prevind(err.format, err.start_color)]))
×
111
    invalid_text = @view err.format[err.start_color:err.end_color]
×
112

113
    printstyled(io, invalid_text, color=:red)
×
114

115
    # +1 is okay, since all format characters are single bytes
116
    println(io, @view(err.format[err.end_color+1:end]), "\"")
×
117

118
    arrow_error = '-'^(length(invalid_text)-1)
×
119
    arrow = "    " * ' '^err.start_color * arrow_error * "^\n"
×
120
    if io_has_color
×
121
        printstyled(io, arrow, color=:red)
×
122
    else
123
        print(io, arrow)
×
124
    end
125
end
126

127
# parse format string
128
function Format(f::AbstractString)
5✔
129
    bytes = codeunits(f)
5✔
130
    len = length(bytes)
5✔
131
    pos = 1
5✔
132
    numarguments = 0
5✔
133

134
    b = 0x00
5✔
135
    local last_percent_pos
136

137
    # skip ahead to first format specifier
138
    while pos <= len
5✔
139
        b = bytes[pos]
5✔
140
        pos += 1
5✔
141
        if b == UInt8('%')
5✔
142
            last_percent_pos = pos-1
5✔
143
            pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, last_percent_pos))
5✔
144
            if bytes[pos] == UInt8('%')
5✔
145
                # escaped '%'
UNCOV
146
                b = bytes[pos]
×
UNCOV
147
                pos += 1
×
148
            else
149
                break
5✔
150
            end
151
        end
UNCOV
152
    end
×
153
    strs = [1:pos - 1 - (b == UInt8('%'))]
5✔
154
    fmts = []
5✔
155
    while pos <= len
10✔
156
        b = bytes[pos]
5✔
157
        pos += 1
5✔
158
        # positioned at start of first format str %
159
        # parse flags
160
        leftalign = plus = space = zero = hash = false
5✔
161
        while true
5✔
162
            if b == UInt8('-')
5✔
UNCOV
163
                leftalign = true
×
164
            elseif b == UInt8('+')
5✔
UNCOV
165
                plus = true
×
166
            elseif b == UInt8(' ')
5✔
UNCOV
167
                space = true
×
168
            elseif b == UInt8('0')
5✔
UNCOV
169
                zero = true
×
170
            elseif b == UInt8('#')
5✔
UNCOV
171
                hash = true
×
172
            else
173
                break
5✔
174
            end
UNCOV
175
            pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, pos-1))
×
UNCOV
176
            b = bytes[pos]
×
UNCOV
177
            pos += 1
×
UNCOV
178
        end
×
179
        if leftalign
5✔
UNCOV
180
            zero = false
×
181
        end
182
        # parse width
183
        width = 0
5✔
184
        dynamic_width = false
5✔
185
        if b == UInt8('*')
5✔
UNCOV
186
            dynamic_width = true
×
UNCOV
187
            numarguments += 1
×
UNCOV
188
            b = bytes[pos]
×
UNCOV
189
            pos += 1
×
190
        else
191
            while b - UInt8('0') < 0x0a
10✔
192
            width = 10 * width + (b - UInt8('0'))
5✔
193
                b = bytes[pos]
5✔
194
                pos += 1
5✔
195
                pos > len && break
5✔
196
            end
5✔
197
        end
198
        # parse precision
199
        precision = 0
5✔
200
        parsedprecdigits = false
5✔
201
        dynamic_precision = false
5✔
202
        if b == UInt8('.')
5✔
203
            pos > len && throw(InvalidFormatStringError("Precision specifier is missing precision", f, last_percent_pos, pos-1))
5✔
204
            parsedprecdigits = true
5✔
205
            b = bytes[pos]
5✔
206
            pos += 1
5✔
207
            if pos <= len
5✔
208
                if b == UInt8('*')
5✔
UNCOV
209
                    dynamic_precision = true
×
UNCOV
210
                    numarguments += 1
×
UNCOV
211
                    b = bytes[pos]
×
UNCOV
212
                    pos += 1
×
213
                else
214
                    precision = 0
5✔
215
                    while b - UInt8('0') < 0x0a
5✔
216
                        precision = 10precision + (b - UInt8('0'))
5✔
217
                        b = bytes[pos]
5✔
218
                        pos += 1
5✔
219
                        pos > len && break
5✔
UNCOV
220
                    end
×
221
                end
222
            end
223
        end
224
        # parse length modifier (ignored)
225
        if b == UInt8('h') || b == UInt8('l')
10✔
UNCOV
226
            prev = b
×
UNCOV
227
            pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1))
×
UNCOV
228
            b = bytes[pos]
×
UNCOV
229
            pos += 1
×
UNCOV
230
            if b == prev
×
UNCOV
231
                pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1))
×
UNCOV
232
                b = bytes[pos]
×
UNCOV
233
                pos += 1
×
234
            end
235
        elseif b in b"Ljqtz" # q was a synonym for ll above, see `man 3 printf`. Not to be used.
5✔
UNCOV
236
            pos > len && throw(InvalidFormatStringError("Length modifier is missing type specifier", f, last_percent_pos, pos-1))
×
237
            b = bytes[pos]
×
238
            pos += 1
×
239
        end
240
        # parse type
241
        !(b in b"diouxXDOUeEfFgGaAcCsSpn") && throw(InvalidFormatStringError("'$(Char(b))' is not a valid type specifier", f, last_percent_pos, pos-1))
5✔
242
        type = Val{Char(b)}
5✔
243
        if type <: Ints && precision > 0
5✔
244
            # note - we should also set zero to false if dynamic precision > 0
245
            # this is taken care of in fmt() for Ints
UNCOV
246
            zero = false
×
247
        elseif (type <: Strings || type <: Chars) && !parsedprecdigits
10✔
UNCOV
248
            precision = -1
×
249
        elseif type <: Union{Val{'a'}, Val{'A'}} && !parsedprecdigits
5✔
UNCOV
250
            precision = -1
×
251
        elseif type <: Floats && !parsedprecdigits
5✔
UNCOV
252
            precision = 6
×
253
        end
254
        numarguments += 1
5✔
255
        push!(fmts, Spec{type}(leftalign, plus, space, zero, hash, width, precision, dynamic_width, dynamic_precision))
5✔
256
        start = pos
5✔
257
        while pos <= len
5✔
UNCOV
258
            b = bytes[pos]
×
UNCOV
259
            pos += 1
×
UNCOV
260
            if b == UInt8('%')
×
UNCOV
261
                last_percent_pos = pos-1
×
UNCOV
262
                pos > len && throw(InvalidFormatStringError("Format specifier is incomplete", f, last_percent_pos, last_percent_pos))
×
UNCOV
263
                if bytes[pos] == UInt8('%')
×
264
                    # escaped '%'
UNCOV
265
                    b = bytes[pos]
×
UNCOV
266
                    pos += 1
×
267
                else
UNCOV
268
                    break
×
269
                end
270
            end
UNCOV
271
        end
×
272
        push!(strs, start:pos - 1 - (b == UInt8('%')))
10✔
273
    end
5✔
274
    return Format(bytes, strs, Tuple(fmts), numarguments)
5✔
275
end
276

277
macro format_str(str)
278
    Format(str)
279
end
280

281
const hex = b"0123456789abcdef"
282
const HEX = b"0123456789ABCDEF"
283

284
# write out a single arg according to format options
285
# char
UNCOV
286
@inline function writechar(buf, pos, c)
×
UNCOV
287
    u = bswap(reinterpret(UInt32, c))
×
UNCOV
288
    while true
×
UNCOV
289
        buf[pos] = u % UInt8
×
UNCOV
290
        pos += 1
×
UNCOV
291
        (u >>= 8) == 0 && break
×
UNCOV
292
    end
×
UNCOV
293
    return pos
×
294
end
295

296

297
@inline function rmdynamic(spec::Spec{T}, args, argp) where {T}
UNCOV
298
    zero, width, precision = spec.zero, spec.width, spec.precision
×
UNCOV
299
    if spec.dynamic_width
×
UNCOV
300
        width = args[argp]::Integer
×
UNCOV
301
        argp += 1
×
302
    end
UNCOV
303
    if spec.dynamic_precision
×
UNCOV
304
        precision = args[argp]::Integer
×
UNCOV
305
        if zero && T <: Ints && precision > 0
×
UNCOV
306
            zero = false
×
307
        end
UNCOV
308
        argp += 1
×
309
    end
UNCOV
310
    (Spec{T}(spec.leftalign, spec.plus, spec.space, zero, spec.hash, width, precision, false, false), argp)
×
311
end
312

313
Base.@constprop :aggressive function fmt(buf, pos, args, argp, spec::Spec{T}) where {T}
UNCOV
314
    spec, argp = rmdynamic(spec, args, argp)
×
UNCOV
315
    (fmt(buf, pos, args[argp], spec), argp+1)
×
316
end
317

UNCOV
318
function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Chars}
×
UNCOV
319
    leftalign, width = spec.leftalign, spec.width
×
UNCOV
320
    c = Char(first(arg))
×
UNCOV
321
    w = textwidth(c)
×
UNCOV
322
    if !leftalign && width > w
×
UNCOV
323
        for _ = 1:(width - w)
×
UNCOV
324
            buf[pos] = UInt8(' ')
×
UNCOV
325
            pos += 1
×
UNCOV
326
        end
×
327
    end
UNCOV
328
    pos = writechar(buf, pos, c)
×
UNCOV
329
    if leftalign && width > w
×
UNCOV
330
        for _ = 1:(width - w)
×
UNCOV
331
            buf[pos] = UInt8(' ')
×
UNCOV
332
            pos += 1
×
UNCOV
333
        end
×
334
    end
UNCOV
335
    return pos
×
336
end
337

338
# strings
UNCOV
339
function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Strings}
×
UNCOV
340
    leftalign, hash, width, prec = spec.leftalign, spec.hash, spec.width, spec.precision
×
UNCOV
341
    str = string(arg)
×
UNCOV
342
    slen = textwidth(str)::Int + (hash ? arg isa AbstractString ? 2 : 1 : 0)
×
UNCOV
343
    op = p = prec == -1 ? slen : min(slen, prec)
×
UNCOV
344
    if !leftalign && width > p
×
UNCOV
345
        for _ = 1:(width - p)
×
UNCOV
346
            buf[pos] = UInt8(' ')
×
UNCOV
347
            pos += 1
×
UNCOV
348
        end
×
349
    end
UNCOV
350
    if hash
×
UNCOV
351
        if arg isa Symbol
×
UNCOV
352
            buf[pos] = UInt8(':')
×
UNCOV
353
            pos += 1
×
UNCOV
354
            p -= 1
×
UNCOV
355
        elseif arg isa AbstractString
×
UNCOV
356
            buf[pos] = UInt8('"')
×
UNCOV
357
            pos += 1
×
UNCOV
358
            p -= 1
×
359
        end
360
    end
UNCOV
361
    for c in str
×
UNCOV
362
        p -= textwidth(c)
×
UNCOV
363
        p < 0 && break
×
UNCOV
364
        pos = writechar(buf, pos, c)
×
UNCOV
365
    end
×
UNCOV
366
    if hash && arg isa AbstractString && p > 0
×
367
        buf[pos] = UInt8('"')
×
368
        pos += 1
×
369
    end
UNCOV
370
    if leftalign && width > op
×
UNCOV
371
        for _ = 1:(width - op)
×
UNCOV
372
            buf[pos] = UInt8(' ')
×
UNCOV
373
            pos += 1
×
UNCOV
374
        end
×
375
    end
UNCOV
376
    return pos
×
377
end
378

379
# integers
UNCOV
380
toint(x) = x
×
UNCOV
381
toint(x::Rational) = Integer(x)
×
382

UNCOV
383
fmt(buf, pos, arg::AbstractFloat, spec::Spec{T}) where {T <: Ints} =
×
384
    fmt(buf, pos, arg, floatfmt(spec))
385

UNCOV
386
function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Ints}
×
UNCOV
387
    leftalign, plus, space, zero, hash, width, prec =
×
388
        spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision
UNCOV
389
    bs = base(T)
×
UNCOV
390
    arg2 = toint(arg)
×
UNCOV
391
    n = i = ndigits(arg2, base=bs, pad=1)
×
UNCOV
392
    neg = arg2 < 0
×
UNCOV
393
    x = arg2 isa Base.BitSigned ? unsigned(abs(arg2)) : abs(arg2)
×
UNCOV
394
    arglen = n + (neg || (plus | space)) +
×
395
        (T == Val{'o'} && hash ? 1 : 0) +
396
        (T == Val{'x'} && hash ? 2 : 0) + (T == Val{'X'} && hash ? 2 : 0)
UNCOV
397
    arglen2 = arglen < width && prec > 0 ? arglen + min(max(0, prec - n), width - arglen) : arglen
×
UNCOV
398
    if !leftalign && !zero && arglen2 < width
×
399
        # pad left w/ spaces
UNCOV
400
        for _ = 1:(width - arglen2)
×
UNCOV
401
            buf[pos] = UInt8(' ')
×
UNCOV
402
            pos += 1
×
UNCOV
403
        end
×
404
    end
UNCOV
405
    if neg
×
UNCOV
406
        buf[pos] = UInt8('-'); pos += 1
×
UNCOV
407
    elseif plus # plus overrides space
×
UNCOV
408
        buf[pos] = UInt8('+'); pos += 1
×
UNCOV
409
    elseif space
×
UNCOV
410
        buf[pos] = UInt8(' '); pos += 1
×
411
    end
UNCOV
412
    if T == Val{'o'} && hash
×
UNCOV
413
        buf[pos] = UInt8('0')
×
UNCOV
414
        pos += 1
×
UNCOV
415
    elseif T == Val{'x'} && hash
×
UNCOV
416
        buf[pos] = UInt8('0')
×
UNCOV
417
        buf[pos + 1] = UInt8('x')
×
UNCOV
418
        pos += 2
×
UNCOV
419
    elseif T == Val{'X'} && hash
×
UNCOV
420
        buf[pos] = UInt8('0')
×
UNCOV
421
        buf[pos + 1] = UInt8('X')
×
UNCOV
422
        pos += 2
×
423
    end
UNCOV
424
    if zero && arglen2 < width
×
UNCOV
425
        for _ = 1:(width - arglen2)
×
UNCOV
426
            buf[pos] = UInt8('0')
×
UNCOV
427
            pos += 1
×
UNCOV
428
        end
×
UNCOV
429
    elseif n < prec
×
UNCOV
430
        for _ = 1:(prec - n)
×
UNCOV
431
            buf[pos] = UInt8('0')
×
UNCOV
432
            pos += 1
×
UNCOV
433
        end
×
UNCOV
434
    elseif arglen < arglen2
×
435
        for _ = 1:(arglen2 - arglen)
×
436
            buf[pos] = UInt8('0')
×
437
            pos += 1
×
438
        end
×
439
    end
UNCOV
440
    while i > 0
×
UNCOV
441
        @inbounds buf[pos + i - 1] = bs == 16 ?
×
442
            (T == Val{'x'} ? hex[(x & 0x0f) + 1] : HEX[(x & 0x0f) + 1]) :
443
            (48 + (bs == 8 ? (x & 0x07) : rem(x, 10)))
UNCOV
444
        if bs == 8
×
UNCOV
445
            x >>= 3
×
UNCOV
446
        elseif bs == 16
×
UNCOV
447
            x >>= 4
×
448
        else
UNCOV
449
            x = oftype(x, div(x, 10))
×
450
        end
UNCOV
451
        i -= 1
×
UNCOV
452
    end
×
UNCOV
453
    pos += n
×
UNCOV
454
    if leftalign && arglen2 < width
×
455
        # pad right
UNCOV
456
        for _ = 1:(width - arglen2)
×
UNCOV
457
            buf[pos] = UInt8(' ')
×
UNCOV
458
            pos += 1
×
UNCOV
459
        end
×
460
    end
UNCOV
461
    return pos
×
462
end
463

464
# floats
465
"""
466
    Printf.tofloat(x)
467

468
Convert an argument to a Base float type for printf formatting.
469
By default, arguments are converted to `Float64` via `Float64(x)`.
470
Custom numeric types that have a conversion to a Base float type
471
that wish to hook into printf formatting can extend this method like:
472

473
```julia
474
Printf.tofloat(x::MyCustomType) = convert_my_custom_type_to_float(x)
475
```
476

477
For arbitrary precision numerics, you might extend the method like:
478

479
```julia
480
Printf.tofloat(x::MyArbitraryPrecisionType) = BigFloat(x)
481
```
482

483
!!! compat "Julia 1.6"
484
    This function requires Julia 1.6 or later.
485
"""
UNCOV
486
tofloat(x) = Float64(x)
×
UNCOV
487
tofloat(x::Base.IEEEFloat) = x
×
UNCOV
488
tofloat(x::BigFloat) = x
×
489

UNCOV
490
_snprintf(ptr, siz, str, arg) =
×
UNCOV
491
    @ccall "libmpfr".mpfr_snprintf(ptr::Ptr{UInt8}, siz::Csize_t, str::Ptr{UInt8};
×
492
                                   arg::Ref{BigFloat})::Cint
493

494
# Arbitrary constant for a maximum number of bytes we want to output for a BigFloat.
495
# 8KiB seems like a reasonable default. Larger BigFloat representations should probably
496
# use a custom printing routine. Printing values with results larger than this ourselves
497
# seems like a dangerous thing to do.
498
const __BIG_FLOAT_MAX__ = 8192
499

UNCOV
500
function fmt(buf, pos, arg, spec::Spec{T}) where {T <: Floats}
×
UNCOV
501
    leftalign, plus, space, zero, hash, width, prec =
×
502
        spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision
UNCOV
503
    x = tofloat(arg)
×
UNCOV
504
    if x isa BigFloat
×
UNCOV
505
        if isfinite(x)
×
UNCOV
506
            GC.@preserve buf begin
×
UNCOV
507
                siz = length(buf) - pos + 1
×
UNCOV
508
                str = string(spec; modifier="R")
×
UNCOV
509
                required_length = _snprintf(pointer(buf, pos), siz, str, x)
×
UNCOV
510
                if required_length > siz
×
UNCOV
511
                    required_length > __BIG_FLOAT_MAX__ &&
×
512
                        throw(ArgumentError("The given BigFloat requires $required_length bytes to be printed, which is more than the maximum of $__BIG_FLOAT_MAX__ bytes supported."))
UNCOV
513
                    resize!(buf, required_length + 1)
×
UNCOV
514
                    required_length = _snprintf(pointer(buf, pos), required_length + 1, str, x)
×
515
                end
UNCOV
516
                required_length > 0 || throw(ArgumentError("The given BigFloat would produce less than the maximum allowed number of bytes $__BIG_FLOAT_MAX__, but still couldn't be printed fully for an unknown reason."))
×
UNCOV
517
                return pos + required_length
×
518
            end
519
        end
UNCOV
520
        x = Float64(x)
×
521
    end
UNCOV
522
    if T == Val{'e'} || T == Val{'E'}
×
UNCOV
523
        newpos = Ryu.writeexp(buf, pos, x, prec, plus, space, hash, char(T), UInt8('.'))
×
UNCOV
524
    elseif T == Val{'f'} || T == Val{'F'}
×
UNCOV
525
        newpos = Ryu.writefixed(buf, pos, x, prec, plus, space, hash, UInt8('.'))
×
UNCOV
526
    elseif T == Val{'g'} || T == Val{'G'}
×
UNCOV
527
        if isinf(x) || isnan(x)
×
UNCOV
528
            newpos = Ryu.writeshortest(buf, pos, x, plus, space)
×
529
        else
530
            # C11-compliant general format
UNCOV
531
            prec = prec == 0 ? 1 : prec
×
532
            # format the value in scientific notation and parse the exponent part
UNCOV
533
            exp = let p = Ryu.writeexp(buf, pos, x, prec)
×
UNCOV
534
                b1, b2, b3, b4 = buf[p-4], buf[p-3], buf[p-2], buf[p-1]
×
UNCOV
535
                Z = UInt8('0')
×
UNCOV
536
                if b1 == UInt8('e')
×
537
                    # two-digit exponent
UNCOV
538
                    sign = b2 == UInt8('+') ? 1 : -1
×
UNCOV
539
                    exp = 10 * (b3 - Z) + (b4 - Z)
×
540
                else
541
                    # three-digit exponent
UNCOV
542
                    sign = b1 == UInt8('+') ? 1 : -1
×
UNCOV
543
                    exp = 100 * (b2 - Z) + 10 * (b3 - Z) + (b4 - Z)
×
544
                end
UNCOV
545
                flipsign(exp, sign)
×
546
            end
UNCOV
547
            if -4 ≤ exp < prec
×
UNCOV
548
                newpos = Ryu.writefixed(buf, pos, x, prec - (exp + 1), plus, space, hash, UInt8('.'), !hash)
×
549
            else
UNCOV
550
                newpos = Ryu.writeexp(buf, pos, x, prec - 1, plus, space, hash, T == Val{'g'} ? UInt8('e') : UInt8('E'), UInt8('.'), !hash)
×
551
            end
552
        end
UNCOV
553
    elseif T == Val{'a'} || T == Val{'A'}
×
UNCOV
554
        x, neg = x < 0 || x === -Base.zero(x) ? (-x, true) : (x, false)
×
UNCOV
555
        newpos = pos
×
UNCOV
556
        if neg
×
UNCOV
557
            buf[newpos] = UInt8('-')
×
UNCOV
558
            newpos += 1
×
UNCOV
559
        elseif plus
×
UNCOV
560
            buf[newpos] = UInt8('+')
×
UNCOV
561
            newpos += 1
×
UNCOV
562
        elseif space
×
UNCOV
563
            buf[newpos] = UInt8(' ')
×
UNCOV
564
            newpos += 1
×
565
        end
UNCOV
566
        if isnan(x)
×
567
            buf[newpos] = UInt8('N')
×
568
            buf[newpos + 1] = UInt8('a')
×
569
            buf[newpos + 2] = UInt8('N')
×
570
            newpos += 3
×
UNCOV
571
        elseif !isfinite(x)
×
572
            buf[newpos] = UInt8('I')
×
573
            buf[newpos + 1] = UInt8('n')
×
574
            buf[newpos + 2] = UInt8('f')
×
575
            newpos += 3
×
576
        else
UNCOV
577
            buf[newpos] = UInt8('0')
×
UNCOV
578
            newpos += 1
×
UNCOV
579
            buf[newpos] = T <: Val{'a'} ? UInt8('x') : UInt8('X')
×
UNCOV
580
            newpos += 1
×
UNCOV
581
            if arg == 0
×
UNCOV
582
                buf[newpos] = UInt8('0')
×
UNCOV
583
                newpos += 1
×
UNCOV
584
                if prec > 0
×
UNCOV
585
                    buf[newpos] = UInt8('.')
×
UNCOV
586
                    newpos += 1
×
UNCOV
587
                    while prec > 0
×
UNCOV
588
                        buf[newpos] = UInt8('0')
×
UNCOV
589
                        newpos += 1
×
UNCOV
590
                        prec -= 1
×
UNCOV
591
                    end
×
592
                end
UNCOV
593
                buf[newpos] = T <: Val{'a'} ? UInt8('p') : UInt8('P')
×
UNCOV
594
                buf[newpos + 1] = UInt8('+')
×
UNCOV
595
                buf[newpos + 2] = UInt8('0')
×
UNCOV
596
                newpos += 3
×
597
            else
UNCOV
598
                if prec > -1
×
UNCOV
599
                    s, p = frexp(x)
×
UNCOV
600
                    sigbits = 4 * min(prec, 13)
×
UNCOV
601
                    s = 0.25 * round(ldexp(s, 1 + sigbits))
×
602
                    # ensure last 2 exponent bits either 01 or 10
UNCOV
603
                    u = (reinterpret(UInt64, s) & 0x003f_ffff_ffff_ffff) >> (52 - sigbits)
×
UNCOV
604
                    i = n = (sizeof(u) << 1) - (leading_zeros(u) >> 2)
×
605
                else
UNCOV
606
                    s, p = frexp(x)
×
UNCOV
607
                    s *= 2.0
×
UNCOV
608
                    u = (reinterpret(UInt64, s) & 0x001f_ffff_ffff_ffff)
×
UNCOV
609
                    t = (trailing_zeros(u) >> 2)
×
UNCOV
610
                    u >>= (t << 2)
×
UNCOV
611
                    i = n = 14 - t
×
612
                end
UNCOV
613
                frac = u > 9 || hash || prec > 0
×
UNCOV
614
                while i > 1
×
UNCOV
615
                    buf[newpos + i] = T == Val{'a'} ? hex[(u & 0x0f) + 1] : HEX[(u & 0x0f) + 1]
×
UNCOV
616
                    u >>= 4
×
UNCOV
617
                    i -= 1
×
UNCOV
618
                    prec -= 1
×
UNCOV
619
                end
×
UNCOV
620
                if frac
×
UNCOV
621
                    buf[newpos + 1] = UInt8('.')
×
622
                end
UNCOV
623
                buf[newpos] = T == Val{'a'} ? hex[(u & 0x0f) + 1] : HEX[(u & 0x0f) + 1]
×
UNCOV
624
                newpos += n + frac
×
UNCOV
625
                while prec > 0
×
626
                    buf[newpos] = UInt8('0')
×
627
                    newpos += 1
×
628
                    prec -= 1
×
629
                end
×
UNCOV
630
                buf[newpos] = T <: Val{'a'} ? UInt8('p') : UInt8('P')
×
UNCOV
631
                newpos += 1
×
UNCOV
632
                p -= 1
×
UNCOV
633
                buf[newpos] = p < 0 ? UInt8('-') : UInt8('+')
×
UNCOV
634
                p = p < 0 ? -p : p
×
UNCOV
635
                newpos += 1
×
UNCOV
636
                n = i = ndigits(p, base=10, pad=1)
×
UNCOV
637
                while i > 0
×
UNCOV
638
                    buf[newpos + i - 1] = 48 + rem(p, 10)
×
UNCOV
639
                    p = oftype(p, div(p, 10))
×
UNCOV
640
                    i -= 1
×
UNCOV
641
                end
×
UNCOV
642
                newpos += n
×
643
            end
644
        end
645
    end
UNCOV
646
    if newpos - pos < width
×
647
        # need to pad
UNCOV
648
        if leftalign
×
649
            # easy case, just pad spaces after number
UNCOV
650
            for _ = 1:(width - (newpos - pos))
×
UNCOV
651
                buf[newpos] = UInt8(' ')
×
UNCOV
652
                newpos += 1
×
UNCOV
653
            end
×
654
        else
655
            # right aligned
UNCOV
656
            n = width - (newpos - pos)
×
UNCOV
657
            if zero && isfinite(x)
×
UNCOV
658
                ex = (arg < 0 || (plus | space)) + (T <: Union{Val{'a'}, Val{'A'}} ? 2 : 0)
×
UNCOV
659
                so = pos + ex
×
UNCOV
660
                len = (newpos - pos) - ex
×
UNCOV
661
                copyto!(buf, so + n, buf, so, len)
×
UNCOV
662
                for i = so:(so + n - 1)
×
UNCOV
663
                    buf[i] = UInt8('0')
×
UNCOV
664
                end
×
UNCOV
665
                newpos += n
×
666
            else
UNCOV
667
                copyto!(buf, pos + n, buf, pos, newpos - pos)
×
UNCOV
668
                for i = pos:(pos + n - 1)
×
UNCOV
669
                    buf[i] = UInt8(' ')
×
UNCOV
670
                end
×
UNCOV
671
                newpos += n
×
672
            end
673
        end
674
    end
UNCOV
675
    return newpos
×
676
end
677

678
# pointers
UNCOV
679
fmt(buf, pos, arg, spec::Spec{Pointer}) = fmt(buf, pos, UInt64(arg), ptrfmt(spec, arg))
×
680

681
# position counters
UNCOV
682
function fmt(buf, pos, arg::Ref{<:Integer}, ::Spec{PositionCounter})
×
UNCOV
683
    arg[] = pos - 1
×
UNCOV
684
    pos
×
685
end
686

687
# old Printf compat
688
function fix_dec end
689
function ini_dec end
690

691
# generic fallback
692
function fmtfallback(buf, pos, arg, spec::Spec{T}) where {T}
×
693
    leftalign, plus, space, zero, hash, width, prec =
×
694
        spec.leftalign, spec.plus, spec.space, spec.zero, spec.hash, spec.width, spec.precision
695
    buf2 = Base.StringVector(
×
696
        MAX_INTEGER_PART_WIDTH + MAX_FRACTIONAL_PART_WIDTH + MAX_FMT_CHARS_WIDTH
697
    )
698
    ise = T <: Union{Val{'e'}, Val{'E'}}
×
699
    isg = T <: Union{Val{'g'}, Val{'G'}}
×
700
    isf = T <: Val{'f'}
×
701
    if isg
×
702
        prec = prec == 0 ? 1 : prec
×
703
        arg = round(arg, sigdigits=prec)
×
704
    end
705
    n, pt, neg = isf ? fix_dec(arg, prec, buf2) : ini_dec(arg, min(prec + ise, length(buf2) - 1), buf2)
×
706
    if isg && !hash
×
707
        while buf2[n] == UInt8('0')
×
708
            n -= 1
×
709
        end
×
710
    end
711
    expform = ise || (isg && !(-4 < pt <= prec))
×
712
    n2 = n + (expform ? 4 : 0) + (prec > 0 || hash) + (neg || (plus | space)) +
×
713
        (isf && pt >= n ? prec + 1 : 0)
714
    if !leftalign && !zero && n2 < width
×
715
        # pad left w/ spaces
716
        for _ = 1:(width - n2)
×
717
            buf[pos] = UInt8(' ')
×
718
            pos += 1
×
719
        end
×
720
    end
721
    if neg
×
722
        buf[pos] = UInt8('-'); pos += 1
×
723
    elseif plus # plus overrides space
×
724
        buf[pos] = UInt8('+'); pos += 1
×
725
    elseif space
×
726
        buf[pos] = UInt8(' '); pos += 1
×
727
    end
728
    if zero && n2 < width
×
729
        for _ = 1:(width - n2)
×
730
            buf[pos] = UInt8('0')
×
731
            pos += 1
×
732
        end
×
733
    end
734
    if expform
×
735
        buf[pos] = buf2[1]
×
736
        pos += 1
×
737
        if n > 1 || hash
×
738
            buf[pos] = UInt8('.')
×
739
            pos += 1
×
740
            for i = 2:n
×
741
                buf[pos] = buf2[i]
×
742
                pos += 1
×
743
            end
×
744
        end
745
        buf[pos] = T <: Val{'e'} || T <: Val{'g'} ? UInt8('e') : UInt8('E')
×
746
        pos += 1
×
747
        exp = pt - 1
×
748
        buf[pos] = exp < 0 ? UInt8('-') : UInt8('+')
×
749
        pos += 1
×
750
        exp = abs(exp)
×
751
        if exp < 10
×
752
            buf[pos] = UInt8('0')
×
753
            buf[pos + 1] = 48 + exp
×
754
            pos += 2
×
755
        else
756
            buf[pos] = 48 + div(exp, 10)
×
757
            buf[pos + 1] = 48 + rem(exp, 10)
×
758
            pos += 2
×
759
        end
760
    elseif pt <= 0
×
761
        buf[pos] = UInt8('0')
×
762
        buf[pos + 1] = UInt8('.')
×
763
        pos += 2
×
764
        while pt < 0
×
765
            buf[pos] = UInt8('0')
×
766
            pos += 1
×
767
            pt += 1
×
768
        end
×
769
        for i = 1:n
×
770
            buf[pos] = buf2[i]
×
771
            pos += 1
×
772
        end
×
773
    elseif pt >= n
×
774
        for i = 1:n
×
775
            buf[pos] = buf2[i]
×
776
            pos += 1
×
777
        end
×
778
        while pt > n
×
779
            buf[pos] = UInt8('0')
×
780
            pos += 1
×
781
            n += 1
×
782
        end
×
783
        if hash || (isf && prec > 0)
×
784
            buf[pos] = UInt8('.')
×
785
            pos += 1
×
786
            while prec > 0
×
787
                buf[pos] = UInt8('0')
×
788
                pos += 1
×
789
                prec -= 1
×
790
            end
×
791
        end
792
    else
793
        for i = 1:pt
×
794
            buf[pos] = buf2[i]
×
795
            pos += 1
×
796
        end
×
797
        buf[pos] = UInt8('.')
×
798
        pos += 1
×
799
        for i = pt+1:n
×
800
            buf[pos] = buf2[i]
×
801
            pos += 1
×
802
        end
×
803
    end
804

805
    if leftalign && n2 < width
×
806
        # pad right
807
        for _ = 1:(width - n2)
×
808
            buf[pos] = UInt8(' ')
×
809
            pos += 1
×
810
        end
×
811
    end
812
    return pos
×
813
end
814

815
const UNROLL_UPTO = 16
816
# if you have your own buffer + pos, write formatted args directly to it
817
@inline function format(buf::Vector{UInt8}, pos::Integer, f::Format, args...)
818
    # write out first substring
UNCOV
819
    escapechar = false
×
UNCOV
820
    for i in f.substringranges[1]
×
UNCOV
821
        b = f.str[i]
×
UNCOV
822
        if !escapechar
×
UNCOV
823
            buf[pos] = b
×
UNCOV
824
            pos += 1
×
UNCOV
825
            escapechar = b === UInt8('%')
×
826
        else
UNCOV
827
            escapechar = false
×
828
        end
UNCOV
829
    end
×
830
    # for each format, write out arg and next substring
831
    # unroll up to 16 formats
UNCOV
832
    N = length(f.formats)
×
UNCOV
833
    argp = 1
×
834
    Base.@nexprs 16 i -> begin
UNCOV
835
        if N >= i
×
UNCOV
836
            pos, argp = fmt(buf, pos, args, argp, f.formats[i])
×
UNCOV
837
            for j in f.substringranges[i + 1]
×
UNCOV
838
                b = f.str[j]
×
UNCOV
839
                if !escapechar
×
UNCOV
840
                    buf[pos] = b
×
UNCOV
841
                    pos += 1
×
UNCOV
842
                    escapechar = b === UInt8('%')
×
843
                else
UNCOV
844
                    escapechar = false
×
845
                end
UNCOV
846
            end
×
847
        end
848
    end
UNCOV
849
    if N > 16
×
UNCOV
850
        for i = 17:length(f.formats)
×
UNCOV
851
            pos, argp = fmt(buf, pos, args, argp, f.formats[i])
×
UNCOV
852
            for j in f.substringranges[i + 1]
×
UNCOV
853
                b = f.str[j]
×
UNCOV
854
                if !escapechar
×
UNCOV
855
                    buf[pos] = b
×
UNCOV
856
                    pos += 1
×
UNCOV
857
                    escapechar = b === UInt8('%')
×
858
                else
859
                    escapechar = false
×
860
                end
UNCOV
861
            end
×
UNCOV
862
        end
×
863
    end
UNCOV
864
    return pos
×
865
end
866

867
@inline function plength(f::Spec{T}, args, argp) where {T}
UNCOV
868
    f, argp = rmdynamic(f, args, argp)
×
UNCOV
869
    (plength(f, args[argp]), argp+1)
×
870
end
871

UNCOV
872
function plength(f::Spec{T}, x) where {T <: Chars}
×
UNCOV
873
    c = Char(first(x))
×
UNCOV
874
    w = textwidth(c)
×
UNCOV
875
    return max(f.width, w) + (ncodeunits(c) - w)
×
876
end
877

UNCOV
878
plength(f::Spec{Pointer}, x) = max(f.width, 2 * sizeof(x) + 2)
×
879

UNCOV
880
function plength(f::Spec{T}, x) where {T <: Strings}
×
UNCOV
881
    str = string(x)
×
UNCOV
882
    sw = textwidth(str)
×
UNCOV
883
    p = f.precision == -1 ? (sw + (f.hash ? (x isa Symbol ? 1 : 2) : 0)) : f.precision
×
UNCOV
884
    return max(f.width, p) + (sizeof(str) - sw)
×
885
end
886

887
function plength(f::Spec{T}, x) where {T <: Ints}
×
UNCOV
888
    x2 = toint(x)
×
UNCOV
889
    return max(
×
890
        f.width,
891
        f.precision + ndigits(x2, base=base(T), pad=1) + MAX_FMT_CHARS_WIDTH
892
    )
893
end
894

UNCOV
895
plength(f::Spec{T}, x::AbstractFloat) where {T <: Ints} =
×
896
    max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + 0 + MAX_FMT_CHARS_WIDTH)
UNCOV
897
plength(f::Spec{T}, x) where {T <: Floats} =
×
898
    max(f.width, f.hash + MAX_INTEGER_PART_WIDTH + f.precision + MAX_FMT_CHARS_WIDTH)
899
plength(::Spec{PositionCounter}, x) = 0
×
900

901
@inline function computelen(substringranges, formats, args)
UNCOV
902
    len = sum(length, substringranges)
×
UNCOV
903
    N = length(formats)
×
904
    # unroll up to 16 formats
UNCOV
905
    argp = 1
×
906
    Base.@nexprs 16 i -> begin
UNCOV
907
        if N >= i
×
UNCOV
908
            l, argp = plength(formats[i], args, argp)
×
UNCOV
909
            len += l
×
910
        end
911
    end
UNCOV
912
    if N > 16
×
UNCOV
913
        for i = 17:length(formats)
×
UNCOV
914
            l, argp = plength(formats[i], args, argp)
×
UNCOV
915
            len += l
×
UNCOV
916
        end
×
917
    end
UNCOV
918
    return len
×
919
end
920

UNCOV
921
@noinline argmismatch(a, b) =
×
922
    throw(ArgumentError("Number of format specifiers and number of provided args differ: $a != $b"))
923

924
"""
925
    Printf.format(f::Printf.Format, args...) => String
926
    Printf.format(io::IO, f::Printf.Format, args...)
927

928
Apply a printf format object `f` to provided `args` and return the formatted string
929
(1st method), or print directly to an `io` object (2nd method). See [`@printf`](@ref)
930
for more details on C `printf` support.
931
"""
932
function format end
933

934
# Since it will specialize on `f`, which has a Tuple-type often of length(args), we might as well specialize on `args` too.
UNCOV
935
function format(io::IO, f::Format, args::Vararg{Any,N}) where N # => Nothing
×
UNCOV
936
    f.numarguments == length(args) || argmismatch(f.numarguments, length(args))
×
UNCOV
937
    buf = Base.StringVector(computelen(f.substringranges, f.formats, args))
×
UNCOV
938
    pos = format(buf, 1, f, args...)
×
UNCOV
939
    write(io, resize!(buf, pos - 1))
×
UNCOV
940
    return
×
941
end
942

UNCOV
943
function format(f::Format, args::Vararg{Any,N}) where N # => String
×
UNCOV
944
    f.numarguments == length(args) || argmismatch(f.numarguments, length(args))
×
UNCOV
945
    buf = Base.StringVector(computelen(f.substringranges, f.formats, args))
×
UNCOV
946
    pos = format(buf, 1, f, args...)
×
UNCOV
947
    return String(resize!(buf, pos - 1))
×
948
end
949

950
"""
951
    @printf([io::IO], "%Fmt", args...)
952

953
Print `args` using C `printf` style format specification string.
954
Optionally, an `IO` may be passed as the first argument to redirect output.
955

956
# Examples
957
```jldoctest
958
julia> @printf "Hello %s" "world"
959
Hello world
960

961
julia> @printf "Scientific notation %e" 1.234
962
Scientific notation 1.234000e+00
963

964
julia> @printf "Scientific notation three digits %.3e" 1.23456
965
Scientific notation three digits 1.235e+00
966

967
julia> @printf "Decimal two digits %.2f" 1.23456
968
Decimal two digits 1.23
969

970
julia> @printf "Padded to length 5 %5i" 123
971
Padded to length 5   123
972

973
julia> @printf "Padded with zeros to length 6 %06i" 123
974
Padded with zeros to length 6 000123
975

976
julia> @printf "Use shorter of decimal or scientific %g %g" 1.23 12300000.0
977
Use shorter of decimal or scientific 1.23 1.23e+07
978

979
julia> @printf "Use dynamic width and precision  %*.*f" 10 2 0.12345
980
Use dynamic width and precision        0.12
981
```
982
For a systematic specification of the format, see [here](https://en.cppreference.com/w/c/io/fprintf).
983
See also [`@sprintf`](@ref) to get the result as a `String` instead of it being printed.
984

985
# Caveats
986
`Inf` and `NaN` are printed consistently as `Inf` and `NaN` for flags `%a`, `%A`,
987
`%e`, `%E`, `%f`, `%F`, `%g`, and `%G`. Furthermore, if a floating point number is
988
equally close to the numeric values of two possible output strings, the output
989
string further away from zero is chosen.
990

991
# Examples
992
```jldoctest
993
julia> @printf("%f %F %f %F", Inf, Inf, NaN, NaN)
994
Inf Inf NaN NaN
995

996
julia> @printf "%.0f %.1f %f" 0.5 0.025 -0.0078125
997
0 0.0 -0.007812
998
```
999

1000
!!! compat "Julia 1.8"
1001
    Starting in Julia 1.8, `%s` (string) and `%c` (character) widths are computed
1002
    using [`textwidth`](@ref), which e.g. ignores zero-width characters
1003
    (such as combining characters for diacritical marks) and treats certain
1004
    "wide" characters (e.g. emoji) as width `2`.
1005

1006
!!! compat "Julia 1.10"
1007
    Dynamic width specifiers like `%*s` and `%0*.*f` require Julia 1.10.
1008
"""
1009
macro printf(io_or_fmt, args...)
1010
    if io_or_fmt isa String
1011
        fmt = Format(io_or_fmt)
1012
        return esc(:($Printf.format(stdout, $fmt, $(args...))))
1013
    else
1014
        io = io_or_fmt
1015
        isempty(args) && throw(ArgumentError("No format string provided to `@printf` - use like `@printf [io] <format string> [<args...>]."))
1016
        fmt_str = first(args)
1017
        fmt_str isa String || throw(ArgumentError("First argument to `@printf` after `io` must be a format string"))
1018
        fmt = Format(fmt_str)
1019
        return esc(:($Printf.format($io, $fmt, $(Base.tail(args)...))))
1020
    end
1021
end
1022

1023
"""
1024
    @sprintf("%Fmt", args...)
1025

1026
Return [`@printf`](@ref) formatted output as string.
1027

1028
# Examples
1029
```jldoctest
1030
julia> @sprintf "this is a %s %15.1f" "test" 34.567
1031
"this is a test            34.6"
1032
```
1033
"""
1034
macro sprintf(fmt, args...)
5✔
1035
    fmt isa String || throw(ArgumentError("First argument to `@sprintf` must be a format string."))
5✔
1036
    f = Format(fmt)
5✔
1037
    return esc(:($Printf.format($f, $(args...))))
5✔
1038
end
1039

1040
end # module
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc