text:
# Functions for filtering availability and hours per week.

function fill_missing_availability(schedule, api_availability, workers, organization, task, send_messages=true)
    #=
    This function merges availability into users, and if availability is
    not set (eg the user forgot to set it) - then it marks them as availabile all the time.
    =#
    w_out = Any[]

    for worker in workers
        if worker["max_shifts_per_week"] == 0
            # No need to send message - this is is expected behavior
            id = worker["id"]
            Logging.info( "Worker id $id has max_shifts 0 - removed")
            continue
        end

        w_availability = nothing
        for a in api_availability
            if a["user_id"] == worker["id"]
                w_availability = a["availability"]
            end
        end
        if w_availability == nothing
            w_availability = Dict()
            for day in days(organization)
                w_availability[day] = ones(Int, length(schedule["demand"][day]))
            end
            name = worker["name"]
            if send_messages
                send_message("$name did not set availability", task)
            end
        end
        worker["availability"] = w_availability
        push!(w_out, worker)
    end
    return w_out
end

function downgrade_availability(schedule, workers, organization, task, send_messages=true)
    w_out = Any[]

    for worker in workers
        # availability and max hours per shift
        max_hours = Dict() # key is day
        week_hour_sum = 0
        days_available = 0
        name = worker["name"]
        # Max hours per week depends on hours per day, which depends on
        for day in days(organization)
            # If business is closed, mark availability as 0
            for t in 1:length(schedule["demand"][day])
                if schedule["demand"][day][t] == 0
                    worker["availability"][day][t] = 0
                end
            end

            day_avail = StaffJoy.anneal_availability(worker["availability"][day], worker["min_shift_length"])

            # This is for app->staffjoy conversion
            worker["shift_time_max"] = worker["max_shift_length"]
            worker["shift_time_min"] = worker["min_shift_length"]
            day_longest = StaffJoy.get_longest_availability(worker, day)
            # Gotta clean up for unit tests :-(
            delete!(worker, "shift_time_max")
            delete!(worker, "shift_time_min")
            max_hours[day] = day_longest
            week_hour_sum += day_longest
            if day_longest > 0
                days_available += 1
            end
        end

        completely_unavailable = false
        adjust_shifts = false
        adjust_hours = false

        # Check if completely unavailable
        if week_hour_sum == 0
            Logging.info( "$name completely unavailable")
            if send_messages
                send_message("$name is completely unavailable to work this week, so they were not scheduled", task)
            end
            continue
        end

        # First round - downgrade total hours for the week
        if week_hour_sum < worker["max_hours_per_week"]
            adjust_hours = true
            worker["max_hours_per_week"] = week_hour_sum
        end

        if week_hour_sum < worker["min_hours_per_week"]
            adjust_hours = true
            worker["min_hours_per_week"] = week_hour_sum
        end

        # Second round - check shift count
        if days_available < worker["max_shifts_per_week"]
            adjust_shifts = true
            worker["max_shifts_per_week"] = days_available
        end

        if days_available < worker["min_shifts_per_week"]
            adjust_shifts = true
            worker["min_shifts_per_week"] = days_available
        end

        if worker["min_shifts_per_week"] > worker["max_shifts_per_week"]
            adjust_shifts = true
            worker["min_shifts_per_week"] = worker["max_shifts_per_week"]
        end

        # Third - see that the shifts * hour bands are within hours per week
        while worker["max_hours_per_week"] < worker["min_shifts_per_week"] * worker["min_shift_length"]
            adjust_shifts = true
            worker["min_shifts_per_week"] -= 1
        end

        if worker["max_hours_per_week"] > worker["max_shifts_per_week"] * worker["max_shift_length"]
            adjust_shifts = true
            worker["max_hours_per_week"] = worker["max_shifts_per_week"] * worker["max_shift_length"]
        end

        if worker["min_hours_per_week"] < worker["min_shifts_per_week"] * worker["min_shift_length"]
            adjust_shifts = true
            worker["min_hours_per_week"] = worker["min_shifts_per_week"] * worker["min_shift_length"]
        end

        if worker["min_shifts_per_week"] > worker["max_shifts_per_week"]
            adjust_shifts = true
            worker["min_shifts_per_week"] = worker["max_shifts_per_week"]
        end
        if worker["min_hours_per_week"] > worker["max_hours_per_week"]
            adjust_hours = true
            worker["min_hours_per_week"] = worker["max_hours_per_week"]
        end

        while worker["min_shifts_per_week"]*worker["max_shift_length"] < worker["min_hours_per_week"]
            # This isn't really a messageable change
            worker["min_shifts_per_week"] += 1
        end

        # Message time
        push!(w_out, worker)

        if adjust_shifts && adjust_hours

            Logging.info( "$name hours and shifts downgraded")
            if send_messages
                send_message("$name is not available to work the set shifts and hours this week, so we decreased them to match their availability.", task)
            end

        elseif adjust_shifts

            Logging.info( "$name shifts downgraded")
            if send_messages
                send_message("$name is not available to work the specified number of shifts this week, so we decreased them to match their availability.", task)
            end

        elseif adjust_hours
            Logging.info( "$name hours downgraded")
            if send_messages
                send_message("$name is not available to work the specified number of hours this week, so we decreased them to match their availability.", task)
            end
        end
    end
    return w_out
end

max_stars_repo_name: shdann/autoshift

max_issues_repo_name: shdann/autoshift

max_forks_repo_name: shdann/autoshift


text:
module Punycoder
base = 36
tmin = 1
tmax = 26
skew = 38
damp = 700
initial_bias = 72
initial_n = 128
code_points = ['a':'z';'0':'9']

function getbasicstring(str)
  return join([isascii(c) ? c : "" for c in str])
end

function adapt(delta, numpoints, firsttime)
  delta = if firsttime
    div(delta, damp)
  else
    div(delta, 2)
  end
  delta += div(delta, numpoints)
  k = 0
  while delta > div((base - tmin) * tmax, 2)
    delta = div(delta, base - tmin)
    k += base
  end # while
  return k + div((base - tmin + 1) * delta, delta + skew)
end

function encode(input)
  n, delta, bias = initial_n, 0, initial_bias
  encoded_string = getbasicstring(input)
  inputLength = length(input)
  h = b = length(encoded_string)
  if h > 0
    encoded_string *= "-"
  end # if
  input = collect(input)
  while h < inputLength
    m = minimum(filter(x->!isascii(x) && x >= n, input))
    delta += Int(m - n) * (h + 1)
    n = m
    for c in input
      if c < n
        delta += 1
      elseif c == n
        q = delta
        for k in base:base:10000000
          t = if k <= bias
                tmin
              elseif k >= bias + tmax
                tmax
              else
                k - bias
              end # if
          if q < t
            break
          end
          encoded_string = string(encoded_string, code_points[t + 1 + ((q - t) % (base - t))])
          q = div((q - t), (base - t))
        end # for
        encoded_string = string(encoded_string, code_points[q + 1])
        bias = adapt(delta, h + 1, h == b)
        delta = 0
        h += 1
      end # if
    end # for
    delta += 1
    n += 1
  end # while
  return encoded_string
end # function

function decode(input)
  n, i, bias = initial_n, 0, initial_bias
  input_parts = split(input, '-')
  output = collect(input_parts[1])
  input = collect(input_parts[end])

  while length(input) > 0
    oldi, w = i, 1
    for k in base:base:10000000
      digit = findfirst(code_points, input[1]) - 1
      i += digit * w
      t = if k <= bias
            tmin
          elseif k >= bias + tmax
            tmax
          else
            k - bias
          end # if
      if digit < t
        break
      end # if
      w *= base - t
      input = input[2:end]
    end # for
    bias = adapt(i - oldi, length(output) + 1, oldi == 0)
    n += div(i, length(output) + 1)
    i = i % (length(output) + 1)
    if i == 0
      splice!(output, 1, [n, output[1]])
    else
      splice!(output, i, [output[i], n])
    end
    i += 1
    input = input[2:end]
  end # while
  return join(output)
end # function

end # module

ext: jl


text:
"""
    tableGeoRegions(; onlycustom::Bool) -> nothing

Display all available GeoRegions in tabular format.

Keyword Arguments
=================

- `onlycustom` : If true, only custom, user-defined GeoRegions will be displayed. To show all available GeoRegions (including predefined GeoRegions), set `onlycustom` to `false`
"""
function tableGeoRegions(;
    onlycustom :: Bool = false
)

    flist = ["rectlist.txt","polylist.txt"]
    if !onlycustom
        flist = vcat(flist,"giorgi.txt","srex.txt","ar6.txt")
    end

    regvec  = []
    filevec = []
    typevec = []

    for fname in flist
        copygeoregions(fname)
        rvec,rtype = listgeoregions(joinpath(DEPOT_PATH[1],"files","GeoRegions",fname))
        regvec = vcat(regvec,rvec)
        nreg = length(rvec)
        fvec = fill(fname,nreg); filevec = vcat(filevec,fvec)
        tvec = fill(rtype,nreg); typevec = vcat(typevec,tvec)
    end

    ngeo = size(regvec,1)
    fmat = Array{Any,2}(undef,ngeo,5)

    for igeo = 1 : ngeo
        geo = getgeoregion(
            regvec[igeo],
            joinpath(DEPOT_PATH[1],"files","GeoRegions",filevec[igeo]),
            typevec[igeo]
        )

        fmat[igeo,1] = geo.regID
        fmat[igeo,2] = typevec[igeo]
        fmat[igeo,3] = geo.name
        fmat[igeo,4] = geo.parID
        fmat[igeo,5] = filevec[igeo]

    end

    head = ["ID","Type","Name","Parent","File"];

    pretty_table(
        fmat,head,
        alignment=[:c,:c,:l,:c,:c],
        crop = :none, tf = tf_compact
    );

    return nothing

end

"""
    tableGeoRegions(fname::AbstractString) -> nothing

Display all available GeoRegions in tabular format.

Keyword Arguments
=================

- `onlycustom` : If true, only custom, user-defined GeoRegions will be displayed. To show all available GeoRegions (including predefined GeoRegions), set `onlycustom` to `false`
"""
function tableGeoRegions(
    fname::AbstractString
)

    rvec,rtype = listgeoregions(fname)
    ngeo = size(rvec,1)
    fmat = Array{Any,2}(undef,ngeo,5)

    for igeo = 1 : ngeo
        g = getgeoregion(rvec[igeo],fname,rtype)
        fmat[igeo,1] = g.regID
        fmat[igeo,2] = rtype
        fmat[igeo,3] = g.name
        fmat[igeo,4] = g.parID
        fmat[igeo,5] = basename(fname)
    end

    head = ["ID","Type","Name","Parent","File"];

    pretty_table(
        fmat,head,
        alignment=[:c,:c,:l,:c,:c],
        crop = :none, tf = tf_compact
    );

    return nothing

end

"""
    tableRectRegions(;
        custom :: Bool = true,
        giorgi :: Bool = false
    ) -> nothing

Display all available RectRegions in tabular format.

Keyword Arguments
=================

- `custom` : If true, display custom user-defined GeoRegions. Default is `true`
- `giorgi` : If true, display Giorgi predefined GeoRegions. Default is `false`
"""
function tableRectRegions(;
    custom :: Bool = true,
    giorgi :: Bool = false
)

    flist = []
    if custom
        flist = vcat(flist,"rectlist.txt")
    end
    if giorgi
        flist = vcat(flist,"giorgi.txt")
    end

    regvec  = []
    filevec = []
    typevec = []

    for fname in flist
        copygeoregions(fname)
        rvec,rtype = listgeoregions(joinpath(DEPOT_PATH[1],"files","GeoRegions",fname))
        regvec = vcat(regvec,rvec)
        nreg = length(rvec)
        fvec = fill(fname,nreg); filevec = vcat(filevec,fvec)
        tvec = fill(rtype,nreg); typevec = vcat(typevec,tvec)
    end

    ngeo = size(regvec,1)
    fmat = Array{Any,2}(undef,ngeo,5)

    for igeo = 1 : ngeo
        geo = getgeoregion(
            regvec[igeo],
            joinpath(DEPOT_PATH[1],"files","GeoRegions",filevec[igeo]),
            typevec[igeo]
        )

        fmat[igeo,1] = geo.regID
        fmat[igeo,2] = typevec[igeo]
        fmat[igeo,3] = geo.name
        fmat[igeo,4] = geo.parID
        fmat[igeo,5] = filevec[igeo]

    end

    head = ["ID","Type","Name","Parent","File"];

    pretty_table(
        fmat,head,
        alignment=[:c,:c,:l,:c,:c],
        crop = :none, tf = tf_compact
    );

    return nothing

end

"""
    tablePolyRegions(;
        custom :: Bool = true,
        srex :: Bool = false,
        ar6  :: Bool = false
    ) -> nothing

Display all available PolyRegions in tabular format.

Keyword Arguments
=================

- `custom` : If true, display custom user-defined GeoRegions. Default is `true`
- `srex` : If true, display SREX predefined GeoRegions. Default is `false`
- `ar6` : If true, display IPCC AR6 predefined GeoRegions. Default is `false`
"""
function tablePolyRegions(;
    custom :: Bool = true,
    srex :: Bool = false,
    ar6  :: Bool = false
)

    flist = []
    if custom
        flist = vcat(flist,"polylist.txt")
    end
    if srex
        flist = vcat(flist,"srex.txt")
    end
    if ar6
        flist = vcat(flist,"ar6.txt")
    end

    regvec  = []
    filevec = []
    typevec = []

    for fname in flist
        copygeoregions(fname)
        rvec,rtype = listgeoregions(joinpath(DEPOT_PATH[1],"files","GeoRegions",fname))
        regvec = vcat(regvec,rvec)
        nreg = length(rvec)
        fvec = fill(fname,nreg); filevec = vcat(filevec,fvec)
        tvec = fill(rtype,nreg); typevec = vcat(typevec,tvec)
    end

    ngeo = size(regvec,1)
    fmat = Array{Any,2}(undef,ngeo,5)

    for igeo = 1 : ngeo
        geo = getgeoregion(
            regvec[igeo],
            joinpath(DEPOT_PATH[1],"files","GeoRegions",filevec[igeo]),
            typevec[igeo]
        )

        fmat[igeo,1] = geo.regID
        fmat[igeo,2] = typevec[igeo]
        fmat[igeo,3] = geo.name
        fmat[igeo,4] = geo.parID
        fmat[igeo,5] = filevec[igeo]

    end

    head = ["ID","Type","Name","Parent","File"];

    pretty_table(
        fmat,head,
        alignment=[:c,:c,:l,:c,:c],
        crop = :none, tf = tf_compact
    );

    return nothing

end


text:
# Histogram (Bar) plots
#
# Vertical bars
function PlotBars(values::Vector{Float32}; count::Integer = length(values),
                  label_id::String = "", width = 0.67, shift = 0.0, offset::Integer = 0,
                  stride::Integer = 1)

    LibCImPlot.PlotBarsFloatPtrInt(label_id, values, Cint(count), Float64(width),
                                        Float64(shift), Cint(offset),
                                        Cint(stride * sizeof(Float32)))
end

function PlotBars(values::Vector{Float64}; count::Integer = length(values),
                  label_id::String = "", width = 0.67, shift = 0.0, offset::Integer = 0,
                  stride::Integer = 1)

    LibCImPlot.PlotBarsdoublePtrInt(label_id, values, Cint(count), Float64(width),
                                          Float64(shift), Cint(offset),
                                          Cint(stride * sizeof(Float64)))
end

function PlotBars(x::Vector{Float32}, y::Vector{Float32};
                  count::Integer = min(length(x),length(y)), label_id::String = "",
                  width = 0.67, offset::Integer = 0, stride::Integer = 1)

    LibCImPlot.PlotBarsFloatPtrFloatPtr(label_id, x, y, Cint(count), Float64(width),
                                        Cint(offset), Cint(stride * sizeof(Float32)))
end

function PlotBars(x::Vector{Float64}, y::Vector{Float64};
                  count::Integer = min(length(x), length(y)), label_id::String = "",
                  width = 0.67, offset::Integer = 0, stride::Integer = 1)

    LibCImPlot.PlotBarsdoublePtrdoublePtr(label_id, x, y, Cint(count), Float64(width),
                                        Cint(offset), Cint(stride * sizeof(Float64)))
end

# Horizontal bars
function PlotBarsH(values::Vector{Float32}; count::Integer = length(values),
                  label_id::String = "", width = 0.67, shift = 0.0, offset::Integer = 0,
                  stride::Integer = 1)

    LibCImPlot.PlotBarsHFloatPtrInt(label_id, values, Cint(count), Float64(width),
                                        Float64(shift), Cint(offset),
                                        Cint(stride * sizeof(Float32)))
end

function PlotBarsH(values::Vector{Float64}; count::Integer = length(values),
                  label_id::String = "", width = 0.67, shift = 0.0, offset::Integer = 0,
                  stride::Integer = 1)

    LibCImPlot.PlotBarsHdoublePtrInt(label_id, values, Cint(count), Float64(width),
                                          Float64(shift), Cint(offset),
                                          Cint(stride * sizeof(Float64)))
end

function PlotBarsH(x::Vector{Float32}, y::Vector{Float32};
                  count::Integer = min(length(x),length(y)), label_id::String = "",
                  width = 0.67, offset::Integer = 0, stride::Integer = 1)

    LibCImPlot.PlotBarsHFloatPtrFloatPtr(label_id, x, y, Cint(count), Float64(width),
                                        Cint(offset), Cint(stride * sizeof(Float32)))
end

function PlotBarsH(x::Vector{Float64}, y::Vector{Float64};
                  count::Integer = min(length(x), length(y)), label_id::String = "",
                  width = 0.67, offset::Integer = 0, stride::Integer = 1)

    LibCImPlot.PlotBarsHdoublePtrdoublePtr(label_id, x, y, Cint(count), Float64(width),
                                        Cint(offset), Cint(stride * sizeof(Float64)))
end


text:
# This simulation is referenced from
# [《量子力学本征值问题的数值方法——打靶法与Numerov方法》](https://zhuanlan.zhihu.com/p/59099100).
using LinearAlgebra: dot

using Roots
using SpecialMatrices: Strang

using NumericalMethodsInQuantumMechanics.EigenvalueProblems
using NumericalMethodsInQuantumMechanics.EigenvalueProblems.NumerovMethod
using NumericalMethodsInQuantumMechanics.EigenvalueProblems.ShootingMethod

x = range(0; stop = 1, step = 0.002)
n = length(x)
g = zeros(n)
s = zeros(n)
problem = setup_problem(BoundaryCondition(0.0, 1.0), x, g, s)
yd0 = shoot(problem, Bisection(), [0, 1000])
ψ = integrate(InitialCondition(0.0, yd0), x, g, s)
e = dot(ψ, Strang(n) * ψ)


text:
using Documenter, MultivariateStats, StatsBase, Statistics, Random, LinearAlgebra

if Base.HOME_PROJECT[] !== nothing
    Base.HOME_PROJECT[] = abspath(Base.HOME_PROJECT[])
end

makedocs(
    sitename = "MultivariateStats.jl",
    modules = [MultivariateStats],
    pages = ["Home"=>"index.md",
             "whiten.md",
             "lreg.md",
             "lda.md",
             "pca.md",
             "ica.md",
             "cca.md",
             "fa.md",
             "mds.md",
             "Development"=>"api.md"]
)

deploydocs(
    repo = "github.com/JuliaStats/MultivariateStats.jl.git"
)


text:
always_print(::Matcher) = false

print_field(m::Matcher, n::Type{Val{N}}) where {N} = "$(N)"

print_matcher(m::Matcher) = print_matcher(m, Set{Matcher}())

# this is optimized to be compact.  it could be prettier with more
# spaces, but then it's not as useful for large grammars.

function print_matcher(m::Matcher, known::Set{Matcher})
    function producer(c::Channel)
        if m in known
            put!(c, "$(m.name)...")
        else
            put!(c, "$(m.name)")
            if !always_print(m)
                push!(known, m)
            end
            names = [n for n in fieldnames(typeof(m)) if n != :name]
            for name in names
                if isa(getfield(m, name), Matcher)
                    for (i, line) = enumerate(print_matcher(getfield(m, name), known))
                        if name == names[end]
                            put!(c, i == 1 ? "`-$(line)" : "  $(line)")
                        else
                            put!(c, i == 1 ? "+-$(line)" : "| $(line)")
                        end
                    end
                elseif isa(getfield(m, name), Array{Matcher,1})
                    for (j, x) in enumerate(getfield(m, name))
                        tag = name == :matchers ? "[$j]" : "$(name)[$j]"
                        for (i, line) = enumerate(print_matcher(getfield(m, name)[j], known))
                            if name == names[end] && j == length(getfield(m, name))
                                put!(c, i == 1 ? "`-$(tag):$(line)" : "  $(line)")
                            else
                                put!(c, i == 1 ? "+-$(tag):$(line)" : "| $(line)")
                            end
                        end
                    end
                else
                    if name == names[end]
                        put!(c, "`-$(print_field(m, Val{name}))")
                    else
                        put!(c, "+-$(print_field(m, Val{name}))")
                    end
                end
            end
        end
    end
    Channel(c -> producer(c))
end

function Base.print(io::Base.IO, m::Matcher)
    print(io, join(print_matcher(m), "\n"))
end


text:
const Labels{N} = NTuple{N,Int}

# Automatically determine the output labels given
# input labels of a contraction
function contract_labels(T1labels::Labels{N1}, T2labels::Labels{N2}) where {N1,N2}
  ncont = 0
  for i in T1labels
    i < 0 && (ncont += 1)
  end
  NR = N1 + N2 - 2 * ncont
  ValNR = Val{NR}
  return contract_labels(ValNR, T1labels, T2labels)
end

function contract_labels(
  ::Type{Val{NR}}, T1labels::Labels{N1}, T2labels::Labels{N2}
) where {NR,N1,N2}
  Rlabels = MVector{NR,Int}(undef)
  u = 1
  # TODO: use Rlabels, don't assume ncon convention
  for i in 1:N1
    if T1labels[i] > 0
      @inbounds Rlabels[u] = T1labels[i]
      u += 1
    end
  end
  for i in 1:N2
    if T2labels[i] > 0
      @inbounds Rlabels[u] = T2labels[i]
      u += 1
    end
  end
  return Labels{NR}(Rlabels)
end

function _contract_inds!(
  Ris, T1is, T1labels::Labels{N1}, T2is, T2labels::Labels{N2}, Rlabels::Labels{NR}
) where {N1,N2,NR}
  for n in 1:NR
    Rlabel = @inbounds Rlabels[n]
    found = false
    for n1 in 1:N1
      if Rlabel == @inbounds T1labels[n1]
        @inbounds Ris[n] = @inbounds T1is[n1]
        found = true
        break
      end
    end
    if !found
      for n2 in 1:N2
        if Rlabel == @inbounds T2labels[n2]
          @inbounds Ris[n] = @inbounds T2is[n2]
          break
        end
      end
    end
  end
  return nothing
end

# Old version that doesn't take into account Rlabels
#function _contract_inds!(Ris,
#                         T1is,
#                         T1labels::Labels{N1},
#                         T2is,
#                         T2labels::Labels{N2},
#                         Rlabels::Labels{NR}) where {N1,N2,NR}
#  ncont = 0
#  for i in T1labels
#    i < 0 && (ncont += 1)
#  end
#  IndT = promote_type(eltype(T1is), eltype(T2is))
#  u = 1
#  # TODO: use Rlabels, don't assume ncon convention
#  for i1 ∈ 1:N1
#    if T1labels[i1] > 0
#      Ris[u] = T1is[i1]
#      u += 1
#    else
#      # This is to check that T1is and T2is
#      # can contract
#      i2 = findfirst(==(T1labels[i1]),T2labels)
#      dir(T1is[i1]) == -dir(T2is[i2]) || error("Attempting to contract index:\n\n$(T1is[i1])\nwith index:\n\n$(T2is[i2])\nIndices must have opposite directions to contract.")
#    end
#  end
#  for i2 ∈ 1:N2
#    if T2labels[i2] > 0
#      Ris[u] = T2is[i2]
#      u += 1
#    end
#  end
#  return nothing
#end

function contract_inds(T1is, T1labels::Labels{0}, T2is, T2labels::Labels{0}, Rlabels)
  return ()
end

# isbitstype that returns a Val for dispatch
isbitsval(T) = Val(isbitstype(T))

function contract_inds(T1is, T1labels, T2is, T2labels, Rlabels)
  IndT = promote_type(eltype(T1is), eltype(T2is))
  return _contract_inds(isbitsval(IndT), IndT, T1is, T1labels, T2is, T2labels, Rlabels)
end

# isbits
function _contract_inds(::Val{true}, IndT, T1is, T1labels, T2is, T2labels, Rlabels)
  Ris = MVector{length(Rlabels),IndT}(undef)
  _contract_inds!(Ris, T1is, T1labels, T2is, T2labels, Rlabels)
  return Tuple(Ris)
end

# !isbits
function _contract_inds(::Val{false}, IndT, T1is, T1labels, T2is, T2labels, Rlabels)
  Ris = SizedVector{length(Rlabels),IndT}(undef)
  _contract_inds!(Ris, T1is, T1labels, T2is, T2labels, Rlabels)
  return Tuple(Ris)
end

mutable struct ContractionProperties{NA,NB,NC}
  ai::NTuple{NA,Int}
  bi::NTuple{NB,Int}
  ci::NTuple{NC,Int}
  nactiveA::Int
  nactiveB::Int
  nactiveC::Int
  AtoB::NTuple{NA,Int}
  AtoC::NTuple{NA,Int}
  BtoC::NTuple{NB,Int}
  permuteA::Bool
  permuteB::Bool
  permuteC::Bool
  dleft::Int
  dmid::Int
  dright::Int
  ncont::Int
  Acstart::Int
  Bcstart::Int
  Austart::Int
  Bustart::Int
  PA::NTuple{NA,Int}
  PB::NTuple{NB,Int}
  PC::NTuple{NC,Int}
  ctrans::Bool
  newArange::NTuple{NA,Int}
  newBrange::NTuple{NB,Int}
  newCrange::NTuple{NC,Int}
  function ContractionProperties(
    ai::NTuple{NA,Int}, bi::NTuple{NB,Int}, ci::NTuple{NC,Int}
  ) where {NA,NB,NC}
    return new{NA,NB,NC}(
      ai,
      bi,
      ci,
      0,
      0,
      0,
      ntuple(_ -> 0, Val(NA)),
      ntuple(_ -> 0, Val(NA)),
      ntuple(_ -> 0, Val(NB)),
      false,
      false,
      false,
      1,
      1,
      1,
      0,
      NA,
      NB,
      NA,
      NB,
      ntuple(i -> i, Val(NA)),
      ntuple(i -> i, Val(NB)),
      ntuple(i -> i, Val(NC)),
      false,
      ntuple(_ -> 0, Val(NA)),
      ntuple(_ -> 0, Val(NB)),
      ntuple(_ -> 0, Val(NC)),
    )
  end
end

function compute_perms!(props::ContractionProperties{NA,NB,NC}) where {NA,NB,NC}
  #leng.th(props.AtoB)!=0 && return

  # Access the fields before the loop
  # since getting fields from the mutable struct
  # takes nontrivial time
  ai = props.ai
  bi = props.bi
  ci = props.ci

  ncont = props.ncont
  AtoB = props.AtoB
  Acstart = props.Acstart
  Bcstart = props.Bcstart
  for i in 1:NA
    for j in 1:NB
      if @inbounds ai[i] == @inbounds bi[j]
        ncont += 1
        #TODO: check this if this should be i,j or i-1,j-1 (0-index or 1-index)
        i <= Acstart && (Acstart = i)
        j <= Bcstart && (Bcstart = j)
        #AtoB[i] = j
        AtoB = setindex(AtoB, j, i)
        break
      end
    end
  end
  props.ncont = ncont
  props.AtoB = AtoB
  props.Acstart = Acstart
  props.Bcstart = Bcstart

  Austart = props.Austart
  AtoC = props.AtoC
  for i in 1:NA
    for k in 1:NC
      if @inbounds ai[i] == @inbounds ci[k]
        #TODO: check this if this should be i,j or i-1,j-1 (0-index or 1-index)
        i <= Austart && (Austart = i)
        #AtoC[i] = k
        AtoC = setindex(AtoC, k, i)
        break
      end
    end
  end
  props.Austart = Austart
  props.AtoC = AtoC

  Bustart = props.Bustart
  BtoC = props.BtoC
  for j in 1:NB
    for k in 1:NC
      if bi[j] == ci[k]
        #TODO: check this if this should be i,j or i-1,j-1 (0-index or 1-index)
        j <= Bustart && (Bustart = j)
        #BtoC[j] = k
        BtoC = setindex(BtoC, k, j)
        break
      end
    end
  end
  props.Bustart = Bustart
  props.BtoC = BtoC

  return nothing
end

function checkACsameord(props::ContractionProperties)::Bool
  AtoC = props.AtoC

  props.Austart >= length(props.ai) && return true
  aCind = props.AtoC[props.Austart]
  for i in 1:length(props.ai)
    if !contractedA(props, i)
      AtoC[i] != aCind && return false
      aCind += 1
    end
  end
  return true
end

function checkBCsameord(props::ContractionProperties)::Bool
  props.Bustart >= length(props.bi) && return true
  bCind = props.BtoC[props.Bustart]
  for i in 1:length(props.bi)
    if !contractedB(props, i)
      props.BtoC[i] != bCind && return false
      bCind += 1
    end
  end
  return true
end

contractedA(props::ContractionProperties, i::Int) = (props.AtoC[i] < 1)
contractedB(props::ContractionProperties, i::Int) = (props.BtoC[i] < 1)
Atrans(props::ContractionProperties) = contractedA(props, 1)
Btrans(props::ContractionProperties) = !contractedB(props, 1)
Ctrans(props::ContractionProperties) = props.ctrans

function compute_contraction_properties!(
  props::ContractionProperties{NA,NB,NC}, A, B, C
) where {NA,NB,NC}
  compute_perms!(props)

  #Use props.PC.size() as a check to see if we've already run this
  #length(props.PC)!=0 && return

  #ra = NA #length(props.ai)
  #rb = NB #length(props.bi)
  #rc = NC #length(props.ci)

  #props.PC = fill(0,rc)

  PC = props.PC
  AtoC = props.AtoC
  BtoC = props.BtoC

  dleft = props.dleft
  dmid = props.dmid
  dright = props.dright

  dleft = 1
  dmid = 1
  dright = 1
  c = 1
  for i in 1:NA
    #if !contractedA(props,i)
    if !(AtoC[i] < 1)
      dleft *= size(A, i)
      #props.PC[props.AtoC[i]] = c
      PC = setindex(PC, c, AtoC[i])
      c += 1
    else
      dmid *= size(A, i)
    end
  end
  for j in 1:NB
    #if !contractedB(props,j)
    if !(BtoC[j] < 1)
      dright *= size(B, j)
      #props.PC[props.BtoC[j]] = c
      PC = setindex(PC, c, BtoC[j])
      c += 1
    end
  end
  props.PC = PC
  props.dleft = dleft
  props.dmid = dmid
  props.dright = dright

  if !is_trivial_permutation(props.PC)
    props.permuteC = true
    if checkBCsameord(props) && checkACsameord(props)
      #Can avoid permuting C by
      #computing Bt*At = Ct
      props.ctrans = true
      props.permuteC = false
    end
  end

  #Check if A can be treated as a matrix without permuting
  props.permuteA = false
  if !(contractedA(props, 1) || contractedA(props, NA))
    #If contracted indices are not all at front or back,
    #will have to permute A
    props.permuteA = true
  else
    #Contracted ind start at front or back, check if contiguous
    #TODO: check that the limits are correct (1-indexed vs. 0-indexed)
    for i in 1:(props.ncont)
      if !contractedA(props, props.Acstart + i - 1)
        #Contracted indices not contiguous, must permute
        props.permuteA = true
        break
      end
    end
  end

  #Check if B is matrix-like
  props.permuteB = false
  if !(contractedB(props, 1) || contractedB(props, NB))
    #If contracted indices are not all at front or back,
    #will have to permute B
    props.permuteB = true
  else
    #TODO: check that the limits are correct (1-indexed vs. 0-indexed)
    for i in 1:(props.ncont)
      if !contractedB(props, props.Bcstart + i - 1)
        #Contracted inds not contiguous, permute
        props.permuteB = true
        break
      end
    end
  end

  if !props.permuteA && !props.permuteB
    #Check if contracted inds. in same order
    #TODO: check these limits are correct
    for i in 1:(props.ncont)
      if props.AtoB[props.Acstart + i - 1] != (props.Bcstart + i - 1)
        #If not in same order,
        #must permute one of A or B
        #so permute the smaller one
        props.dleft < props.dright ? (props.permuteA = true) : (props.permuteB = true)
        break
      end
    end
  end

  if props.permuteC && !(props.permuteA && props.permuteB)
    PCost(d::Real) = d * d
    #Could avoid permuting C if
    #permute both A and B, worth it?
    pCcost = PCost(props.dleft * props.dright)
    extra_pABcost = 0
    !props.permuteA && (extra_pABcost += PCost(props.dleft * props.dmid))
    !props.permuteB && (extra_pABcost += PCost(props.dmid * props.dright))
    if extra_pABcost < pCcost
      props.permuteA = true
      props.permuteB = true
      props.permuteC = false
    end
  end

  if props.permuteA
    #props.PA = fill(0,ra)
    #Permute contracted indices to the front,
    #in the same order as on B

    AtoC = props.AtoC
    BtoC = props.BtoC
    ai = props.ai
    bi = props.bi
    PA = props.PA

    newi = 0
    bind = props.Bcstart
    for i in 1:(props.ncont)
      while !(BtoC[bind] < 1)
        bind += 1
      end
      j = findfirst(==(bi[bind]), ai)
      #props.PA[newi + 1] = j
      PA = setindex(PA, j, newi + 1)
      bind += 1
      newi += 1
    end
    #Reset p.AtoC:
    #fill!(props.AtoC,0)
    AtoC = ntuple(_ -> 0, Val(NA))
    #Permute uncontracted indices to
    #appear in same order as on C
    #TODO: check this is correct for 1-indexing
    for k in 1:NC
      j = findfirst(==(props.ci[k]), props.ai)
      if !isnothing(j)
        #props.AtoC[newi+1] = k
        AtoC = setindex(AtoC, k, newi + 1)
        #props.PA[newi+1] = j
        PA = setindex(PA, j, newi + 1)
        newi += 1
      end
      newi == NA && break
    end
    props.PA = PA
    props.AtoC = AtoC
  end

  ##Also update props.Austart,props.Acstart

  Acstart = props.Acstart
  Austart = props.Austart
  newArange = props.newArange
  PA = props.PA

  Acstart = NA + 1
  Austart = NA + 1
  #TODO: check this is correct for 1-indexing
  for i in 1:NA
    #if contractedA(props,i)
    if @inbounds AtoC[i] < 1
      Acstart = min(i, Acstart)
    else
      Austart = min(i, Austart)
    end
    #props.newArange = permute_extents([size(A)...],props.PA)
    newArange = permute(size(A), PA) #[size(A)...][props.PA]
  end
  props.Acstart = Acstart
  props.Austart = Austart
  props.newArange = newArange

  if (props.permuteB)
    PB = props.PB
    AtoC = props.AtoC
    BtoC = props.BtoC
    ai = props.ai
    bi = props.bi
    ci = props.ci
    Bcstart = props.Bcstart
    Bustart = props.Bustart

    #props.PB = fill(0,rb)
    #TODO: check this is correct for 1-indexing
    newi = 0 #1

    if (props.permuteA)
      #A's contracted indices already set to
      #be in same order as B above, so just
      #permute contracted indices to the front
      #keeping relative order

      i = props.Bcstart
      while newi < props.ncont
        while !(BtoC[i] < 1)
          i += 1
        end
        #props.PB[newi+1] = i
        PB = setindex(PB, i, newi + 1)
        i += 1
        newi += 1
      end
    else
      #Permute contracted indices to the
      #front and in same order as on A

      aind = props.Acstart
      for i in 0:(props.ncont - 1)
        while !(AtoC[aind] < 1)
          aind += 1
        end
        j = findfirst(==(ai[aind]), bi)
        #props.PB[newi + 1] = j
        PB = setindex(PB, j, newi + 1)
        aind += 1
        newi += 1
      end
    end

    #Reset p.BtoC:
    #fill!(props.BtoC,0)
    BtoC = ntuple(_ -> 0, Val(NB))

    #Permute uncontracted indices to
    #appear in same order as on C
    for k in 1:NC
      j = findfirst(==(ci[k]), bi)
      if !isnothing(j)
        #props.BtoC[newi + 1] = k
        BtoC = setindex(BtoC, k, newi + 1)
        #props.PB[newi + 1] = j
        PB = setindex(PB, j, newi + 1)
        newi += 1
      end
      newi == NB && break
    end
    Bcstart = NB
    Bustart = NB
    for i in 1:NB
      if BtoC[i] < 1
        Bcstart = min(i, Bcstart)
      else
        Bustart = min(i, Bustart)
      end
    end
    #props.newBrange = permute_extents([size(B)...],props.PB)
    #props.newBrange = [size(B)...][props.PB]
    props.newBrange = permute(size(B), PB)

    props.BtoC = BtoC
    props.PB = PB
    props.Bcstart = Bcstart
    props.Bustart = Bustart
  end

  if props.permuteA || props.permuteB
    AtoC = props.AtoC
    BtoC = props.BtoC
    PC = props.PC

    #Recompute props.PC
    c = 1
    #TODO: check this is correct for 1-indexing
    for i in 1:NA
      AtoC_i = AtoC[i]
      if !(AtoC_i < 1)
        #props.PC[props.AtoC[i]] = c
        PC = setindex(PC, c, AtoC_i)
        c += 1
      end
    end
    #TODO: check this is correct for 1-indexing
    for j in 1:NB
      BtoC_j = BtoC[j]
      if !(BtoC_j < 1)
        #props.PC[props.BtoC[j]] = c
        PC = setindex(PC, c, BtoC_j)
        c += 1
      end
    end
    props.PC = PC

    props.ctrans = false
    if (is_trivial_permutation(PC))
      props.permuteC = false
    else
      props.permuteC = true
      #Here we already know since pc_triv = false that
      #at best indices from B precede those from A (on result C)
      #so if both sets remain in same order on C
      #just need to transpose C, not permute it
      if checkBCsameord(props) && checkACsameord(props)
        props.ctrans = true
        props.permuteC = false
      end
    end
  end

  if props.permuteC
    Rb = MVector{NC,Int}(undef) #Int[]
    k = 1
    AtoC = props.AtoC
    BtoC = props.BtoC
    if !props.permuteA
      #TODO: check this is correct for 1-indexing
      for i in 1:NA
        if !(AtoC[i] < 1)
          #push!(Rb,size(A,i))
          Rb[k] = size(A, i)
          k = k + 1
        end
      end
    else
      #TODO: check this is correct for 1-indexing
      for i in 1:NA
        if !(AtoC[i] < 1)
          #push!(Rb,size(props.newArange,i))
          Rb[k] = props.newArange[i]
          k = k + 1
        end
      end
    end
    if !props.permuteB
      #TODO: check this is correct for 1-indexing
      for j in 1:NB
        if !(BtoC[j] < 1)
          #push!(Rb,size(B,j))
          Rb[k] = size(B, j)
          k = k + 1
        end
      end
    else
      #TODO: check this is correct for 1-indexing
      for j in 1:NB
        if !(BtoC[j] < 1)
          #push!(Rb,size(props.newBrange,j))
          Rb[k] = props.newBrange[j]
          k = k + 1
        end
      end
    end
    props.newCrange = Tuple(Rb)
  end
end


text:
const max_methods = 4  # maximum number of MethodInstances tracked for a particular :call statement

"""
    framecode, lenv = get_call_framecode(fargs, parentframe::JuliaFrameCode, idx::Int)

Return the framecode and environment for a call specified by `fargs = [f, args...]` (see [`prepare_args`](@ref)).
`parentframecode` is the caller, and `idx` is the program-counter index.
If possible, `framecode` will be looked up from the local method tables of `parentframe`.
"""
function get_call_framecode(fargs, parentframe::JuliaFrameCode, idx::Int)
    nargs = length(fargs)  # includes f as the first "argument"
    # Determine whether we can look up the appropriate framecode in the local method table
    if isassigned(parentframe.methodtables, idx)  # if this is the first call, this may not yet be set
        tme = tme1 = parentframe.methodtables[idx]
        local tmeprev
        depth = 1
        while true
            # TODO: consider using world age bounds to handle cache invalidation
            # Determine whether the argument types match the signature
            sig = tme.sig.parameters::SimpleVector
            if length(sig) == nargs
                matches = true
                for i = 1:nargs
                    if !isa(fargs[i], sig[i])
                        matches = false
                        break
                    end
                end
                if matches
                    # Rearrange the list to place this method first
                    # (if we're in a loop, we'll likely match this one again on the next iteration)
                    if depth > 1
                        parentframe.methodtables[idx] = tme
                        tmeprev.next = tme.next
                        tme.next = tme1
                    end
                    # The framecode is stashed in the `inferred` field of the MethodInstance
                    mi = tme.func::MethodInstance
                    return mi.inferred::JuliaFrameCode, mi.sparam_vals
                end
            end
            depth += 1
            tmeprev = tme
            tme = tme.next
            tme === nothing && break
            tme = tme::TypeMapEntry
        end
    end
    # We haven't yet encountered this argtype combination and need to look it up by dispatch
    fargs[1] = f = to_function(fargs[1])
    if isa(f, Core.Builtin)
        # See TODO in optimize!
        return f(fargs[2:end]...), nothing  # for code that has a direct call to a builtin
    end
    # HACK: don't recurse into inference. Inference sometimes returns SSAValue objects and this
    # seems to confuse lookup_var.
    if f === Base._return_type
        return Base._return_type(fargs[2:end]...), nothing
    end
    framecode, args, env, argtypes = prepare_call(f, fargs)
    # Store the results of the method lookup in the local method table
    tme = ccall(:jl_new_struct_uninit, Any, (Any,), TypeMapEntry)::TypeMapEntry
    tme.func = mi = ccall(:jl_new_struct_uninit, Any, (Any,), MethodInstance)::MethodInstance
    tme.sig = mi.specTypes = argtypes
    tme.isleafsig = true
    tme.issimplesig = false
    method = framecode.scope::Method
    tme.va = method.isva
    mi.def = method
    mi.rettype = Any
    mi.sparam_vals = env
    mi.inferred = framecode   # a slight abuse, but not insane
    if isassigned(parentframe.methodtables, idx)
        tme.next = parentframe.methodtables[idx]
        # Drop the oldest tme, if necessary
        tmetmp = tme.next
        depth = 2
        while isdefined(tmetmp, :next) && tmetmp.next !== nothing
            depth += 1
            tmetmp = tmetmp.next
            depth >= max_methods && break
        end
        if depth >= max_methods
            tmetmp.next = nothing
        end
    else
        tme.next = nothing
    end
    parentframe.methodtables[idx] = tme
    return framecode, env
end


text:
import PyCall

z = [rand(10, 100, 10^n) for n in 1:scale]

function from_julia(i)
	return z[i]
end

function to_julia(arr)
	return nothing
end

function bidirectional(arr)
	return arr
end

max_stars_repo_name: pearsonlab/improv

max_issues_repo_name: pearsonlab/improv

max_forks_repo_name: pearsonlab/improv


text:
struct BoardState
    board::String
    csol::String
    position::Int
end

function move(s::BoardState, dpos)
    buffer = Vector{UInt8}(deepcopy(s.board))
    if s.board[s.position] == '@'
        buffer[s.position] = ' '
    else
        buffer[s.position] = '.'
    end
    newpos = s.position + dpos
    if s.board[newpos] == ' '
        buffer[newpos] = '@'
    else
        buffer[newpos] = '+'
    end
    String(buffer)
end

function push(s::BoardState, dpos)
    newpos = s.position + dpos
    boxpos = newpos + dpos
    if s.board[boxpos] != ' ' && s.board[boxpos] != '.'
        return ""
    end
    buffer = Vector{UInt8}(deepcopy(s.board))
    if s.board[s.position] == '@'
        buffer[s.position] = ' '
    else
        buffer[s.position] = '.'
    end
    if s.board[newpos] == '$'
        buffer[newpos] = '@'
    else
        buffer[newpos] = '+'
    end
    if s.board[boxpos] == ' '
        buffer[boxpos] = '$'
    else
        buffer[boxpos] = '*'
    end
    String(buffer)
end

function solve(board)
    width = findfirst("\n", board[2:end])[1] + 1
    dopt = (u = -width, l = -1, d = width, r = 1)
    visited = Dict(board => true)
    open::Vector{BoardState} = [BoardState(board, "", findfirst("@", board)[1])]
    while length(open) > 0
        s1 = open[1]
        open = open[2:end]
        for dir in keys(dopt)
            newpos = s1.position + dopt[dir]
            x = s1.board[newpos]
            if x == '$' || x == '*'
                newboard = push(s1, dopt[dir])
                if newboard == "" || haskey(visited, newboard)
                    continue
                end
                newsol = s1.csol * uppercase(string(dir))
                if findfirst(r"[\.\+]", newboard) ==  nothing
                    return newsol
                end
            elseif x == ' ' || x == '.'
                newboard = move(s1, dopt[dir])
                if haskey(visited, newboard)
                    continue
                end
                newsol = s1.csol * string(dir)
            else
                continue
            end
            open = push!(open, BoardState(newboard, newsol, newpos))
            visited[newboard] = true
        end
    end
    "No solution" # we should only get here if no solution to the sokoban
end

const testlevel = strip(raw"""
#######
#     #
#     #
#. #  #
#. $$ #
#.$$  #
#.#  @#
#######""")
println("For sokoban level:\n$testlevel\n...solution is :\n$(solve(testlevel))")

max_stars_repo_name: ethansaxenian/RosettaDecode

max_issues_repo_name: ethansaxenian/RosettaDecode

max_forks_repo_name: ethansaxenian/RosettaDecode


text:
# src/{PROJECT_NAME}.jl
# {PROJECT_DESCRIPTION}

__precompile__(true)

"""```
module {PROJECT_NAME}
```
{PROJECT_DESCRIPTION}
See https://github.com/{USERNAME}/{PROJECT_NAME}.jl for more details.
"""
module {PROJECT_NAME}

end  # module {PROJECT_NAME}

max_stars_repo_name: JuliaTagBot/julia-template

max_issues_repo_name: JuliaTagBot/julia-template

max_forks_repo_name: JuliaTagBot/julia-template


text:
using Test, InMemoryDatasets, PooledArrays, Random, CategoricalArrays

@testset "make_unique - from DataFrames"  begin
    @test IMD.make_unique([:x, :x, :x_1, :x2], makeunique=true) == [:x, :x_2, :x_1, :x2]
    @test_throws ArgumentError IMD.make_unique([:x, :x, :x_1, :x2], makeunique=false)
    @test IMD.make_unique([:x, :x_1, :x2], makeunique=false) == [:x, :x_1, :x2]
    @test IMD.make_unique([:x, :x, :x_3, :x, :y, :x, :y, :x_1], makeunique = true) == [:x, :x_2, :x_3, :x_4, :y, :x_5, :y_1, :x_1]
end

@testset "repeat count- from DataFrames" begin
    ds = Dataset(a=1:2, b=3:4)
    ref = Dataset(a=repeat(1:2, 2),
                    b=repeat(3:4, 2))
    @test repeat(ds, 2) == ref
    @test repeat(view(ds, 1:2, :), 2) == ref

    @test_throws ArgumentError repeat(ds, 0)
    @test_throws ArgumentError repeat(ds, -1)
end

@testset "repeat inner_outer- from DataFrames" begin
    ds = Dataset(a=1:2, b=3:4)
    ref = Dataset(a=repeat(1:2, inner=2, outer=3),
                    b=repeat(3:4, inner=2, outer=3))
    @test repeat(ds, inner=2, outer=3) == ref
    @test repeat(view(ds, 1:2, :), inner=2, outer=3) == ref

    @test_throws ArgumentError  repeat(ds, inner=2, outer=0)
    @test_throws ArgumentError repeat(ds, inner=0, outer=3)
    @test_throws ArgumentError repeat(ds, inner=2, outer=false)
    @test_throws ArgumentError repeat(ds, inner=false, outer=3)
    @test_throws ArgumentError repeat(ds, inner=2, outer=-1)
    @test_throws ArgumentError repeat(ds, inner=-1, outer=3)
end

@testset "repeat! count- from DataFrames" begin
    ds = Dataset(a=1:2, b=3:4)
    ref = Dataset(a=repeat(1:2, 2),
                    b=repeat(3:4, 2))
    a = ds.a
    b = ds.b
    repeat!(ds, 2)
    @test ds == ref
    @test a == 1:2
    @test b == 3:4

    for v in (0, false)
        ds = Dataset(a=1:2, b=3:4)
        @test_throws ArgumentError repeat!(ds, v)

    end

    ds = Dataset(a=1:2, b=3:4)
    @test_throws ArgumentError repeat(ds, -1)
    @test ds == Dataset(a=1:2, b=3:4)

    @test_throws MethodError repeat!(view(ds, 1:2, :), 2)
end

@testset "repeat! inner_outer- from DataFrames" begin
    ds = Dataset(a=1:2, b=3:4)
    ref = Dataset(a=repeat(1:2, inner=2, outer=3),
                    b=repeat(3:4, inner=2, outer=3))
    a = ds.a
    b = ds.b
    repeat!(ds, inner = 2, outer = 3)
    @test ds == ref
    @test a == 1:2
    @test b == 3:4

    for v in (0, false)
        ds = Dataset(a=1:2, b=3:4)
        @test_throws ArgumentError repeat!(ds, inner=2, outer=v)

        ds = Dataset(a=1:2, b=3:4)
        @test_throws ArgumentError repeat!(ds, inner=v, outer=3)
    end

    ds = Dataset(a=1:2, b=3:4)
    @test_throws ArgumentError repeat(ds, inner = 2, outer = -1)
    @test_throws ArgumentError repeat(ds, inner = -1, outer = 3)
    @test ds == Dataset(a=1:2, b=3:4)

    @test_throws MethodError repeat!(view(ds, 1:2, :), inner = 2, outer = 3)
end

@testset "funname- from DataFrames" begin
    @test IMD.funname(sum ∘ skipmissing ∘ Base.div12) ==
          :sum_skipmissing_div12
end

@testset "repeat - passing `freq`" begin
    ds = Dataset(x1 = [1,2, missing, 0], x2 = PooledArray([1,3,1,2]), x3 = [1.2,1.2,1.1,-10.0], x4 = [2,1,1,1])
    @test repeat(ds, freq = 2) == ds[[1,2,2,2,3,4,4], :]
    @test repeat(ds, freq = :x2) == ds[[1,2,2,2,3,4,4], :]
    @test repeat(ds, freq = :x4) == ds[[1,1,2,3,4], :]
    @test repeat(ds, freq = ds[!, :x4]) == ds[[1,1,2,3,4], :]
    @test repeat(view(ds, :, :), freq = :x4) == ds[[1,1,2,3,4], :]
    @test repeat(view(ds, :, :), freq = 4) == ds[[1,1,2,3,4], :]
    @test_throws ArgumentError repeat(ds, freq = :x1)
    @test_throws ArgumentError repeat(ds, freq = 3)

    repeat!(ds, 1000)
    @test repeat(ds, freq = 2) == ds[repeat([1,2,2,2,3,4,4], 1000), :]
    @test repeat(ds, freq = :x2) == ds[repeat([1,2,2,2,3,4,4], 1000), :]
    @test repeat(ds, freq = :x4) == ds[repeat([1,1,2,3,4],1000), :]
    @test repeat(ds, freq = ds[!, :x4]) == ds[repeat([1,1,2,3,4],1000), :]

     ds = Dataset(a = 0:2, b = 2:4)
     @test repeat(ds, freq = :a) == Dataset(a = [1,2,2], b = [3,4,4])
     @test repeat(ds, freq = [2,0,2]) == Dataset(a = [0,0,2,2], b=[2,2,4,4])

     @test repeat(ds, freq = [1000, 1000, 0]) == Dataset(a = [fill(0,1000);fill(1, 1000)], b=[fill(2,1000);fill(3, 1000)])
     @test repeat(view(ds,[1,2,3], [1,2]), freq = [1000, 1000, 0]) == Dataset(a = [fill(0,1000);fill(1, 1000)], b=[fill(2,1000);fill(3, 1000)])
     @test repeat(view(ds,[1,2,3], [1,2]), freq = [1000, 1000, 0], view  = true) == Dataset(a = [fill(0,1000);fill(1, 1000)], b=[fill(2,1000);fill(3, 1000)])
end


text:
using WindMixing
using JLD2
using Plots

train_files = ["diurnal"]

𝒟train = WindMixing.data(train_files, scale_type=ZeroMeanUnitVarianceScaling, enforce_surface_fluxes=false)
𝒟train.T.coarse
𝒟train.t

file = jldopen("Data/three_layer_constant_fluxes_linear_hr192_Qu5.0e-04_Qb4.0e-08_f1.0e-04_Nh256_Nz128_diurnal_8days_statistics.jld2")

keys(file["timeseries/wT"])
file["timeseries/wT/1973"][:]
file["timeseries/wT/serialized/location"]
VIDEO_NAME = "u_v_T_3sim_-1e-3_-8e-4_-5e-4_diffusivity_1e-1_Ri_1e-1_weights_divide1f5_gradient_smallNN_scale_1e-2_rate_2e-4_test_-1e-3"
VIDEO_NAME = "test_video"

plot(file["timeseries/wT/292"][:], -128:1:0, label="")

close(file)

animate_training_data_profiles_fluxes(train_files, joinpath(FILE_PATH, VIDEO_NAME))

function a(x, y)
    x-y
end

file = jldopen("D:\\Downloads\\test.jld2", "w") do file
    file["function"] = a(1,2)
end

read_file = jldopen("D:\\Downloads\\test.jld2")
read_file["function"]

read_file["function"](1,2)

close(read_file)


text:
"""
State machine for these tests:

@startuml
state TestShallowHistoryMachine {
    state TestShallowHistoryStartState
    state TestShallowHistoryL1State {
        state TestShallowHistoryL2State {
            state TestShallowHistoryL3State
        }
    }
}
@enduml
"""

using Logging, Test, HSM
include("state_transition_test_utils.jl")

@transition_tests_state TestShallowHistoryMachine
@transition_tests_state TestShallowHistoryStartState
@transition_tests_state TestShallowHistoryL1State
@transition_tests_state TestShallowHistoryL2State
@transition_tests_state TestShallowHistoryL3State

function build_shallow_history_machine()
    machine = TestShallowHistoryMachine(nothing)
    start_state = TestShallowHistoryStartState(machine)
    l1_state = TestShallowHistoryL1State(machine)
    l2_state = TestShallowHistoryL2State(l1_state)
    l3_state = TestShallowHistoryL3State(l2_state)

    HSM.transition_to_state!(machine, start_state)

    return machine, start_state, l1_state, l2_state, l3_state
end

#
# Shallow history transition to a state that has not been entered before is the
# transitioned-to state.
#
@testset "transition_to_shallow_history!() -- start -shallow-> l1 => l1" begin
    machine, start_state, l1_state, l2_state, l3_state = build_shallow_history_machine()
    HSM.transition_to_shallow_history!(machine, l1_state)
    @test HSM.active_state(machine) == l1_state
end

#
# Shallow history transition to a state whose substate's child has been entered
# before is the substate.
#
@testset "transition_to_shallow_history!() -- start -> l3 -> start -shallow-> l1 => l2" begin
    machine, start_state, l1_state, l2_state, l3_state = build_shallow_history_machine()
    HSM.transition_to_state!(machine, l3_state)
    HSM.transition_to_state!(machine, start_state)
    HSM.transition_to_shallow_history!(machine, l1_state)
    @test HSM.active_state(machine) == l2_state
end


text:
# This file was generated by the Julia Swagger Code Generator
# Do not modify this file directly. Modify the swagger specification instead.

struct LogsApi <: SwaggerApi
    client::Swagger.Client
end

"""

Param: logpath::String (required)
Return: Nothing
"""
function logFileHandler(_api::LogsApi, logpath::String; _mediaType=nothing)
    _ctx = Swagger.Ctx(_api.client, "GET", Nothing, "/logs/{logpath}", ["BearerToken"])
    Swagger.set_param(_ctx.path, "logpath", logpath)  # type String
    Swagger.set_header_accept(_ctx, [])
    Swagger.set_header_content_type(_ctx, (_mediaType === nothing) ? [] : [_mediaType])
    Swagger.exec(_ctx)
end

"""

Return: Nothing
"""
function logFileListHandler(_api::LogsApi; _mediaType=nothing)
    _ctx = Swagger.Ctx(_api.client, "GET", Nothing, "/logs/", ["BearerToken"])
    Swagger.set_header_accept(_ctx, [])
    Swagger.set_header_content_type(_ctx, (_mediaType === nothing) ? [] : [_mediaType])
    Swagger.exec(_ctx)
end

export logFileHandler, logFileListHandler


text:
"""
# UnixIO.jl

Unix IO Interface.

For Julia programs that need to interact with Unix-specific IO interfaces.

e.g. Character devices, Terminals, Unix domain sockets, Block devices etc.

    using UnixIO
    using UnixIO: C

    UnixIO.read(`curl https://julialang.org`, String; timeout=5)

    io = UnixIO.open("/dev/ttyUSB0", C.O_RDWR | C.O_NOCTTY)
    UnixIO.tcsetattr(io) do attr
        attr.speed=9600
        attr.c_lflag |= C.ICANON
    end
    readline(io; timeout=5)

    fd = C.open("file.txt", C.O_CREAT | C.O_WRONLY, 0o644)
    C.write(fd, pointer("Hello!"), 7)
    C.close(fd)

    io = UnixIO.open("file.txt", C.O_CREAT | C.O_WRONLY)
    write(io, "Hello!")
    close(io)

Blocking IO is multiplexed by running
[`poll(2)`](https://man7.org/linux/man-pages/man2/poll.2.html)
under a task started by `Threads.@spawn`.
See [`src/poll.jl`](src/poll.jl)

If `ENV["JULIA_IO_EVENT_SOURCE"]` is set to `epoll` the
Linux [`epoll(7)`](https://man7.org/linux/man-pages/man7/epoll.7.html)
API is used instead.

If `ENV["JULIA_IO_EVENT_SOURCE"]` is set to `sleep` IO polling
is done by a dumb loop with a 10ms delay. This may be more efficient
for small systems with simple IO requirements.
(e.g. communicating with a few serial ports and sub-processes on a
Raspberry Pi).
"""
module UnixIO

export DuplexIO,
       @sh_str

# External Dependencies.

using Base: ImmutableDict,
            assert_havelock,
            @lock,
            C_NULL

using Preferences
using ReadmeDocs               # for README""" ... """ doc strings.
using Preconditions            # for @require and @ensure contracts
using AsyncLog                 # for @asynclog -- log errors in async tasks.
import TypeTree                # for pretty-printed type trees in doc strings.
const typetree = TypeTree.tt

# Local Modules.

using DuplexIOs
using IOTraits
using UnixIOHeaders
const C = UnixIOHeaders

include("macroutils.jl")
include("ccall.jl")
include("errors.jl")
include("debug.jl")
include("stat.jl")

@db function __init__()

    @ccall(jl_generating_output()::Cint) == 1 && return

    debug_init()
    @db 1 "UnixIO.DEBUG_LEVEL = $DEBUG_LEVEL. See `src/debug.jl`."
    @db 1 "                     0: All `@db` messages off (replaced by `:()`)."
    @db 1 "                     1: high level API calls."
    @db 1 "                     2: Log event registration and polling."
    @db 1 "                     3: Log extra internal functions."
    @db 1 "                     4: Log event polling detail."
    @db 1 "                     5: Log local variable state."
    @db 1 "                     6: Extra verbose."

    poll_queue_init()
    atexit(kill_all_processes)

    global stdin
    global stdout
    global stderr
    stdin = FD{In}(RawFD(Base.STDIN_NO))
    stdout = FD{Out}(RawFD(Base.STDOUT_NO))
    stderr = FD{Out}(RawFD(Base.STDERR_NO))
end

# Deadlines / Timeouts.

macro with_timeout(fd, timeout, ex)
    quote
        fd = $(esc(fd))
        timeout = $(esc(timeout))
        old_deadline = fd.deadline
        new_deadline = timeout + time()
        if new_deadline < old_deadline
            fd.deadline = new_deadline
            if fd.deadline != Inf
                @db 1 "Set fd.deadline: $(db_t(fd.deadline)) ($timeout)"
            end
        end
        try
            $(esc(ex))
        finally
            fd.deadline = old_deadline
            if fd.deadline != Inf && fd.deadline != old_deadline
                @db 1 "Reset fd.deadline: $(db_t(fd.deadline))"
            end
        end
    end
end

# Unix File Descriptor wrapper.

abstract type FDType end

abstract type IODirection end
struct In <: IODirection end
struct Out <: IODirection end

mutable struct FD{D<:IODirection,T<:FDType} <: IO
    fd::RawFD
    isclosed::Bool
    nwaiting::Int
    ready::Base.ThreadSynchronizer
    closed::Base.ThreadSynchronizer
    timeout::Float64
    deadline::Float64
    gothup::Bool
    buffer::IOBuffer
    extra::ImmutableDict{Symbol,Any}

    FD{D}(fd) where D = FD{D,Union{}}(fd)

    function FD{D,T}(fd) where {D,T}
        fcntl_setfl(fd, C.O_NONBLOCK)
        fcntl_setfd(fd, C.O_CLOEXEC)
        _T = (T != Union{}) ? T : fdtype(fd)
        new{D,_T}(RawFD(fd),
                  false,
                  0,
                  Base.ThreadSynchronizer(),
                  Base.ThreadSynchronizer(),
                  Inf,
                  Inf,
                  false,
                  PipeBuffer(),
                  ImmutableDict{Symbol,Any}())
    end
end

const AnyFD = Union{FD, RawFD, Cint}

set_extra(fd::FD, key::Symbol, value) =
    fd.extra = ImmutableDict(fd.extra, key => value)

function get_extra(fd::FD, key::Symbol, default)
    x = get(fd.extra, key, nothing)
    if x == nothing
        x = default()
        set_extra(fd, key, x)
    end
    return x
end

abstract type File     <: FDType end
abstract type Stream   <: FDType end
abstract type MetaFile <: FDType end

abstract type S_IFBLK  <: File end
abstract type S_IFREG  <: File end
abstract type S_IFIFO  <: Stream end
abstract type S_IFCHR  <: Stream end
abstract type S_IFSOCK <: Stream end
abstract type S_IFLNK  <: MetaFile end
abstract type S_IFDIR  <: MetaFile end

struct PidFD           <: FDType end
struct Pseudoterminal  <: S_IFCHR end
struct CanonicalMode   <: S_IFCHR end

Base.wait(fd::FD) = wait(fd, WaitingMechanism(fd))
Base.wait(fd::FD, ::WaitUsingEPoll) = wait_for_event(epoll_queue, fd)
Base.wait(fd::FD, ::WaitUsingPosixPoll) = wait_for_event(poll_queue, fd)

mutable struct Process
    pid::C.pid_t
    in::FD
    out::FD
    stopped::Bool
    code::Union{Nothing, Cint}
    exit_status::Union{Nothing, Cint}
    signal::Union{Nothing, Cint}
end

isstopped(p::Process) = p.stopped
isrunning(p::Process) = isalive(p) && !isstopped(p)
isalive(p::Process) = p.exit_status == nothing &&
                      (p.signal == nothing || p.stopped)
didexit(p::Process) = p.exit_status != nothing
waskilled(p::Process) = !isstopped(p) && p.signal != nothing

IOTraits.WaitingMechanism(::Type{Process}) = firstvalid(WaitUsingPidFD(),
                                                        WaitBySleeping())

@db function Base.wait(p::Process; timeout=Inf,
                                   deadline=timeout+time())
    if !isrunning(p)
        @db return p "Already stopped or terminated"
    end
    wait(p, WaitingMechanism(p); deadline)
    @ensure time() >= deadline || !isrunning(p)
    return p
end

check(p::Process) = wait(p; deadline=0.0)

Base.wait(p::Process, ::WaitBySleeping; kw...) = waitpid(p; kw...)
Base.wait(p::Process, ::WaitUsingPidFD; kw...)= waitpidfd(p; kw...)

function fdtype(fd)

    s = stat(fd)
    t = isfile(s)     ? S_IFREG  :
        isblockdev(s) ? S_IFBLK  :
        ischardev(s)  ? S_IFCHR  :
        isdir(s)      ? S_IFDIR  :
        isfifo(s)     ? S_IFIFO  :
        islink(s)     ? S_IFLNK  :
        issocket(s)   ? S_IFSOCK :
                        Nothing

    if t == S_IFCHR && ispt(fd)
        t = Pseudoterminal
    elseif t == S_IFCHR
        attr=Ref(C.termios_m())
        if @cerr(allow=(C.EINVAL, C.ENOTTY, C.ENODEV),
                 C.tcgetattr_m(fd, attr)) != -1 &&
            (attr[].c_lflag & C.ICANON) != 0
            t = CanonicalMode
        end
    end
    return t
end

stattype(fd) = (s = stat(fd); isfile(s)     ? S_IFREG  :
                              isblockdev(s) ? S_IFBLK  :
                              ischardev(s)  ? S_IFCHR  :
                              isdir(s)      ? S_IFDIR  :
                              isfifo(s)     ? S_IFIFO  :
                              islink(s)     ? S_IFLNK  :
                              issocket(s)   ? S_IFSOCK :
                                              Nothing)

IOTraits.WaitingMechanism(::Type{<:FD{<:Any,<:Union{Stream, PidFD}}}) =
    IOTraits.preferred_poll_mechanism

    # FIXME unify with open() ?
@db 3 function FD(fd, flags = fcntl_getfl(fd); events=nothing)
    @nospecialize

    r = flags & C.O_RDWR   != 0 ?  DuplexIO(FD{In}(fd), FD{Out}(C.dup(fd))) :
        flags & C.O_WRONLY != 0 ?  FD{Out}(fd) :
                                   FD{In}(fd)
    @db 3 return r
end

debug_tiny(x::FD) = string(x)

Base.stat(fd::FD) = stat(fd.fd)
Base.lock(fd::FD) = lock(fd.ready)
Base.unlock(fd::FD) = unlock(fd.ready)
Base.notify(fd::FD, a...) = notify(fd.ready, a...)
Base.islocked(fd::FD) = islocked(fd.ready)
Base.assert_havelock(fd::FD) = Base.assert_havelock(fd.ready)

Base.convert(::Type{Cint}, fd::FD) = Base.cconvert(Cint, fd.fd)
Base.convert(::Type{Cuint}, fd::FD) = Cuint(convert(Cint, fd))
Base.convert(::Type{RawFD}, fd::FD) = RawFD(fd.fd)

include("ReadFD.jl")
include("WriteFD.jl")

IOTraits.ReadFragmentation(::Type{FD{In,CanonicalMode}}) = ReadsLines()

IOTraits.TransferSizeMechanism(::Type{<:FD{In,<:Stream}}) = SupportsFIONREAD()
IOTraits.TotalSize(::Type{<:FD{In,<:File}}) = VariableTotalSize()
IOTraits.TransferSizeMechanism(::Type{<:FD{In,<:File}}) = SuppoutsStatSize()

DuplexIOs.DuplexIO(i::FD{In}, o::FD{Out}) = @invoke DuplexIO(i::IO, o::IO)
DuplexIOs.DuplexIO(o::FD{Out}, i::FD{In}) = @invoke DuplexIO(i::IO, o::IO)

README"## Opening and Closing Unix Files."

@doc README"""
### `UnixIO.open` -- Open Files.

    UnixIO.open([FDType], pathname, [flags = C.O_RDWR],
                                    [mode = 0o644]];
                                    [timeout=Inf],
                                    [tcattr=nothing]) -> UnixIO.FD{FDType}

Open the file specified by pathname.

Use `Base.close` to close the file.

The `IO` returned by `UnixIO.open` can be used with
`UnixIO.read` and `UnixIO.write`. It can also be used with
the standard `Base.IO` functions
(`Base.read`, `Base.write`, `Base.readbytes!`, `Base.close` etc).
See [open(2)](https://man7.org/linux/man-pages/man2/open.2.html)

`open` returns `Unix.FD{FDType}`, where `FDType` is one of:

```
$(join(typetree(FDType; mod=UnixIO)))
```

If the `FDType` argument is provided then `open` guarantees to return
`UnixIO{FDType}` (or throw an `ArgumentError` if `FDType` is not applicable
to `pathname`.

_Note: `C.O_NONBLOCK` is always added to `flags` to ensure compatibility with
[`poll(2)`](https://man7.org/linux/man-pages/man2/poll.2.html).
A `RawFD` can be opened in blocking mode by calling `C.open` directly._
"""
@db 1 function open(type::Type{T},
                    pathname, flags=nothing, mode=0o644;
                    timeout=Inf, tcattr=nothing) where T

    if flags == nothing # See Open Type test set.
        flags = C.O_RDWR
    end

    flags |= C.O_NONBLOCK

    if flags & C.O_RDWR == C.O_RDWR
        flags &= ~C.O_RDWR
        fout = FD{Out,T}(open_raw(pathname, flags | C.O_WRONLY, mode; tcattr))
        fin = FD{In,T}(open_raw(pathname, flags; tcattr))
        io = DuplexIO(fin, fout)
    else
        D = (flags & C.O_WRONLY) != 0 ? Out : In
        io = FD{D,T}(open_raw(pathname, flags, mode; tcattr))
    end

    if timeout != nothing
        set_timeout(io, timeout)
    end

    @ensure isopen(io)
    @db 1 return io
end

open(args...; kw...) = open(Union{}, args...; kw...)

function open_raw(a...; tcattr=nothing)
    fd = @cerr gc_safe_open(a...)
    if tcattr != nothing
        tcsetattr(tcattr, fd)
    end
    return fd
end

gc_safe_open(a...) = @gc_safe C.open(a...)

@doc README"""
### `UnixIO.set_timeout` -- Configure Timeouts.

    UnixIO.set_timeout(fd::UnixIO.FD, timeout)

Configure `fd` to limit IO operations to `timeout` seconds.
"""
set_timeout(fd::FD, t) = fd.timeout = t
DuplexIOs.@wrap set_timeout

"""
Get the file status flags.
Uses `F_GETFL` to read the current flags.
See [fcntl(2)](https://man7.org/linux/man-pages/man2/fcntl.2.html).
"""
fcntl_getfl(fd; get=C.F_GETFL) = @cerr C.fcntl(fd, get)

"""
Set `flag` in the file status flags.
Uses `F_GETFL` to read the current flags and `F_SETFL` to store the new flag.
See [fcntl(2)](https://man7.org/linux/man-pages/man2/fcntl.2.html).
"""
@db 3 function fcntl_setfl(fd, flag; get=C.F_GETFL, set=C.F_SETFL)
    flags = fcntl_getfl(fd; get=get)               ;@db 3 "$(db_c(flag, "O_"))"
    if flags & flag == flag
        return nothing
    end
    flags |= flag
    @cerr C.fcntl(fd, set, flags)
end

fcntl_setfd(fd, flag) = fcntl_setfl(fd, flag; get=C.F_GETFD, set=C.F_SETFD)

@doc README"""
    UnixIO.tcgetattr(tty) -> C.termios_m

Get terminal device options.
See [tcgetattr(3)](https://man7.org/linux/man-pages/man3/tcgetattr.3.html).
"""
@db 2 function tcgetattr(tty, attr=termios(tty))
    aref = Ref(attr)
    @cerr C.tcgetattr_m(tty, aref)
    @db 2 return aref[]
end
tcgetattr(tty::FD{<:Any,<:S_IFCHR}) = tcgetattr(tty, termios(tty))

function termios(tty::FD{<:Any,<:S_IFCHR})
    get_extra(tty, :termios, ()->tcgetattr(tty, C.termios_m()))
end
termios(tty::Union{Cint,RawFD}) = tcgetattr(tty, C.termios_m())

@doc README"""
### `UnixIO.tcsetattr` -- Configure Terminals and Serial Ports.

    UnixIO.tcsetattr(tty, attr::C.termios_m)
    UnixIO.tcsetattr(tty) do attr
         [attr.c_iflag = ...]
         [attr.c_oflag = ...]
         [attr.c_cflag = ...]
         [attr.c_lflag = ...]
         [attr.speed = ...]
    end

Set terminal device options.

e.g.

    io = UnixIO.open("/dev/ttyUSB0", C.O_RDWR | C.O_NOCTTY)
    UnixIO.tcsetattr(io) do attr
        setraw(attr)
        attr.speed=9600
        attr.c_lflag |= C.ICANON
    end

See [tcsetattr(3)](https://man7.org/linux/man-pages/man3/tcsetattr.3.html)
for flag descriptions.

"""
function tcsetattr(f, tty::AnyFD)
    c = tcgetattr(tty)
    f(c)
    tcsetattr(tty, c)
end
tcsetattr(tty, attr::C.termios_m) =
    @cerr C.tcsetattr_m(tty, C.TCSANOW, Ref(attr))

DuplexIOs.@wrap tcsetattr
tcsetattr(f, tty::DuplexIO) = (tcsetattr(f, tty.in); tcsetattr(f, tty.out))

"""
Disabling all terminal input and output processing.
"""
@selfdoc function setraw(x::C.termios_m)
    x.c_iflag = 0
    x.c_oflag = 0
    x.c_cflag = C.CS8
    x.c_lflag = 0
end

function setspeed!(attr::C.termios_m, speed)
    flag = eval(:(C.$(Symbol("B$speed"))))
    @cerr C.cfsetspeed_m(Ref(attr), flag)
    nothing
end

Base.setproperty!(c::C.termios_m, f::Symbol, x) =
    f === :speed ? (setspeed!(c, x); x) :
                    @invoke setproperty!(c::Any, f::Symbol, x)

iscanon(tty) = (termios(tty).c_lflag & C.ICANON) != 0
setcanon(tty) = tcsetattr(c -> c.c_lflag &= C.ICANON, tty)

@doc README"""
### `UnixIO.flush` -- Discard untransmitted data.

    tcflush(tty, flags)

See [tcflush(3p)](https://man7.org/linux/man-pages/man3/tcflush.3p.html)
"""
tcflush(tty, flags) = @cerr C.tcflush(tty, flags)

DuplexIOs.@wrap tcflush

tcdrain(tty) = @cerr C.tcdrain(tty)

DuplexIOs.@wrap tcdrain

@doc README"""
### `UnixIO.tiocgwinsz` -- Get Terminal size.

    tiocgwinsz(tty) -> C.winsize

See [tty_ioctl(4)](https://man7.org/linux/man-pages/man4/tty_ioctl.4.html)
"""
tiocgwinsz(tty) = (x=[C.winsize()]; @cerr C.ioctl(tty, C.TIOCGWINSZ, x); x[1])

tiocgwinsz(tty::DuplexIO) = tiocgwinsz(tty.out)

@db 1 function Base.close(fd::FD)
    fd.isclosed = true
    @dblock fd notify(fd)
    yield()
    shutdown(fd)
    @cerr allow=C.EBADF C.close(fd)
    @dblock fd.closed notify(fd.closed)
    @ensure !isopen(fd)
    nothing
end

@db function Base.wait(fd::FD{In,Pseudoterminal})
    assert_havelock(fd)

    process = fd.extra[:pt_client]

    while true
        timer = register_timer(time() + 1) do
            @dblock fd notify(fd, :poll_isalive) # FIXME SIGCHILD?
        end
        try
            event = @invoke wait(fd::FD{In})
            if event != :poll_isalive
                return event
            end
            #FIXME no need to poll for PidFD?
            if !isalive(check(process))
                return nothing
            end                                              ;@db event process
        finally
            close(timer)
        end
    end
end

@db 1 function Base.close(fd::FD{In,Pseudoterminal})
    C.close(fd.extra[:pt_clientfd])
    @invoke Base.close(fd::FD{In})
end

@db 1 function Base.close(fd::FD{Out,Pseudoterminal})
    if !fd.isclosed                                           ;@db 1 "-> ^D 🛑"
        fd.gothup = true
        # FIXME tcdrain ?
        if iscanon(fd)
            # First CEOF terminates a possible un-terminated line.
            # Second CEOF signals terminal closing.
            for _ in 1:2
                @cerr allow=C.EIO C.write(fd, Ref(UInt8(C.CEOF)), 1)
            end
        end
    end
    @invoke Base.close(fd::FD{Out})
end

Base.isopen(fd::FD) = !fd.isclosed

@db 1 function Base.wait_close(fd::FD; timeout=fd.timeout,
                                       deadline=timeout+time())
    wait_until(fd.closed, ()->!isopen(fd), deadline)
end

@doc README"""
### `UnixIO.shutdown` -- Signal end of transmission or reception.

    UnixIO.shutdown(sockfd, how)

Shut down part of a full-duplex connection.
`how` is one of `C.SHUT_RD`, `C.SHUT_WR` or `C.SHUT_RDWR`.
See [shutdown(2)](https://man7.org/linux/man-pages/man2/shutdown.2.html)
"""
@db 1 function shutdown(fd, how);                  @db 1 "$(db_c(how, "SHUT"))"
    @cerr(allow=(C.ENOTCONN, C.EBADF, C.ENOTSOCK),
          C.shutdown(fd, how))
end

README"## Reading from Unix Files."

@doc README"""
### `UnixIO.read` -- Read bytes into a buffer.

    UnixIO.read(fd, buf, [count=length(buf)];
                [timeout=Inf] ) -> number of bytes read

Attempt to read up to count bytes from file descriptor `fd`
into the buffer starting at `buf`.
See [read(2)](https://man7.org/linux/man-pages/man2/read.2.html)
"""
@db 2 function read(fd::FD{In}, buf::Ptr{UInt8}, count::Csize_t;
                    timeout=fd.timeout)

    # First read from buffer.
    n = bytesavailable(fd.buffer)
    if n > 0                                          ;@db 2 "read from buffer"
        n = min(n, count)
        unsafe_read(fd.buffer, buf, n)
    end

    # Then read from file.
    if count > n
        n += @with_timeout(fd, timeout, transfer(fd, buf+n, count-n))
    end
    @ensure n <= count
    @db 2 return n
end

@db 2 function read(fd::FD, v::AbstractVector{UInt8},
                    index=1, count=length(v)-(index-1); kw...)
    checkbounds(v, index + count - 1)
    buf = pointer(v, index)
    @db 2 return GC.@preserve v read(fd, buf, Csize_t(count); kw...)
end

"""
Read or write (FD{In} or FD{Out}) up to `count` bytes to or from `buf`
until `fd.deadline`.
Always attempt at least one call to `read(2)/write(2)`
even if `fd.deadline` has passed.
Return number of bytes transferred or `0` on timeout.
"""
@db 2 function transfer(fd::FD, buf::Ptr{UInt8}, count::Csize_t)
    @require !fd.isclosed
    @require count > 0

    n = nointr_transfer(fd, buf, count)
    if n >= 0
        @db 2 return n
    end

    @dblock fd while time() < fd.deadline
        wait(fd)
        n = nointr_transfer(fd, buf, count);                           ;@db 2 n
        if n >= 0
            @ensure n <= count
            @db 2 return n
        end                                                    ;@db 2 "wait..."
    end

    @db 2 return 0 "timeout!"
end

@db 2 function transfer(fd::FD, v::AbstractVector{UInt8},
                        index=1, count=length(v)-(index-1))
    checkbounds(v, index + count - 1)
    buf = pointer(v, index)
    @db 2 return GC.@preserve v transfer(fd, buf, Csize_t(count))
end

"""
Read or write (FD{In} or FD{Out}) up to `count` bytes to or from `buf`.
Retry on `C.EINTR`.
Return number of bytes transferred or `-1` on `C.EAGAIN`.
"""
@db 2 function nointr_transfer(fd::FD, buf::Ptr{UInt8}, count::Csize_t)
    @require count > 0
    while true
        n = @cerr(allow=(C.EAGAIN, C.EINTR), raw_transfer(fd, buf, count))
        n != -1 || @db 2 n errno() errname(errno())
        if n != -1 || errno() == C.EAGAIN
            @ensure n <= count
            @db 2 return n
        end
    end
end

include("timer.jl")
include("poll.jl")

README"## Writing to Unix Files."

@doc README"""
### `UnixIO.write` -- Write bytes from a buffer.

    UnixIO.write(fd, buf, [count=length(buf)];
                 [timeout=Inf] ) -> number of bytes written

Write up to count bytes from `buf` to the file referred to by
the file descriptor `fd`.
See [write(2)](https://man7.org/linux/man-pages/man2/write.2.html)
"""
#=
@db 1 function write(fd::WriteFD, buf, count=length(buf); timeout=fd.timeout)
    n = @with_timeout(fd, timeout, transfer(fd, buf, 1, count))
    @db 1 return n
end
=#

@doc README"""
### `UnixIO.println` -- Write messages to the terminal.

    UnixIO.println(x...)
    UnixIO.printerr(x...)

Write directly to `STDOUT` or `STDERR`.
Does not yield control from the current task.
"""
println(x...) = raw_println(Base.STDOUT_NO, x...)
printerr(x...) = raw_println(Base.STDERR_NO, x...)
function raw_println(fd, x...)
    io = IOBuffer()
    Base.println(io, x...)
    buf = take!(io)
    GC.@preserve buf debug_write(fd, pointer(buf), length(buf))
    nothing
end

README"## Unix Domain Sockets."

@doc README"""
### `UnixIO.socketpair()` -- Unix Domain Sockets for IPC.

    UnixIO.socketpair() -> fd1, fd2

Create a pair of connected Unix Domain Sockets (`AF_UNIX`, `SOCK_STREAM`).
See [socketpair(2)](https://man7.org/linux/man-pages/man2/socketpair.2.html)
"""
@db function socketpair()
    v = fill(RawFD(-1), 2)
    @cerr C.socketpair(C.AF_UNIX, C.SOCK_STREAM, 0, v)
    @ensure -1 ∉ v
    @db return (v[1], v[2])
end

@doc README"""
### `UnixI in debug.openpt()` -- Open a pseudoterminal device.

    UnixIO.openpt([flags = C._NOCTTY | C.O_RDWR]) -> ptfd::UnixIO.FD, "/dev/pts/X"

Open an unused pseudoterminal device, returning:
a file descriptor that can be used to refer to that device,
and the path of the pseudoterminal device.

See [posix_openpt(3)](https://man7.org/linux/man-pages/man2/posix_openpt.3.html)
and [prsname(3)](https://man7.org/linux/man-pages/man2/prsname.3.html).
"""
@db function openpt(flags = C.O_NOCTTY | C.O_RDWR)

    pt = @cerr C.posix_openpt(flags)
    @assert ispt(pt)
    @cerr C.grantpt(pt)
    @cerr C.unlockpt(pt)

    path = ptsname(pt)
    clientfd = @cerr C.open(path, C.O_RDONLY | C.O_NOCTTY)
    @assert !ispt(clientfd)

    fd = FD(pt)
    tcsetattr(fd.in) do attr
        setraw(attr)
        attr.c_lflag |= C.ICANON
    end
    set_extra(fd.in, :pt_clientfd, clientfd)

    @db return fd, path
end

function ptsname(fd)
    buf = Vector{UInt8}(undef, 100)
    p = pointer(buf)
    GC.@preserve @cerr C.ptsname_r(fd, p, length(buf))
    unsafe_string(p)
end

ispt(fd) = C.ptsname(fd) != C_NULL

README"## Executing Unix Commands."

@doc README"""
### `sh"cmd"` -- Shell command string.

    sh"shell command"

String containing result of shell command. e.g.

    julia> println("Machine is ", sh"uname -m")
    Machine is x86_64

    julia> println("V: ", sh"grep version Project.toml | awk '{print\$3}'")
    V: "0.1.0"
"""
macro sh_str(s)
    s = Meta.parse("\"$s\"")
    cmd = `bash -c "$s"`
    esc(:($cmd |> read |> String |> chomp))
end

@doc README"""
### `UnixIO.system` -- Run a shell command.

    UnixIO.system(command) -> exit status

See [system(3)](https://man7.org/linux/man-pages/man3/system.3.html)

e.g.
```
julia> UnixIO.system("uname -srm")
Darwin 20.3.0 x86_64
```
"""
@db function system(command)
    r = @cerr gc_safe_system(command)
    if r != 0
        throw(ErrorException("UnixIO.system termination status: $r"))
    end
    @db return r
end

system(cmd::Cmd) = system(join(cmd.exec, " "))

gc_safe_system(c) = @gc_safe C.system(c)

@doc README"""
### `UnixIO.open(::Cmd) do...` -- Communicate with a sub-process.

    UnixIO.open(f, cmd::Cmd; [check_status=true, capture_stderr=false])

Run `cmd` using `posix_spawn`.

Call `f(cmdin, cmdout)`
with (STDIN, STDOUT) of `cmd` connected to (`cmdin`, `cmdout`).

If `capture_stderr` is `true` STDERR of `cmd` is merged into `cmdout`.

If `check_status` is `true` an exception is thrown on non-zero exit status.

e.g.
```
julia> UnixIO.open(`hexdump -C`) do cmdin, cmdout
           write(cmdin, "Hello World!")
           close(cmdin)
           read(cmdout, String)
       end |> println
00000000  48 65 6c 6c 6f 20 57 6f  72 6c 64 21              |Hello World!|
0000000c
```
"""
@db function open(f, cmd::Cmd; capture_stderr=false, env=nothing, kw...)
    process = socketpair_spawn(cmd; env, capture_stderr)
    run_cmd_function(f, cmd, process; kw...)
end

@doc README"""
### `UnixIO.ptopen(::Cmd) do...` -- Run a sub-process in a pseudoterminal.

    UnixIO.ptopen(f, cmd::Cmd; [env=ENV, check_status=true])

Run `cmd` using `posix_spawn`.

Call `f(cmdin, cmdout)` with (STDIN, STDOUT and STDERR) of `cmd`
connected to (`cmdin`, `cmdout`) via a pseudoterminal.

If `check_status` is `true` an exception is thrown on non-zero exit status.

Run `cmd` using `posix_spawn`.
"""
@db function ptopen(f, cmd::Cmd; env=nothing, kw...)
    process = pseudoterminal_spawn(cmd; env)
    run_cmd_function(f, cmd, process; kw...)
end

@db function run_cmd_function(f, cmd, p; check_status=true)
    @nospecialize

    # Run the IO handling function `f`.
    try                                         ;@db 1 "f ┬─($(p.in),$(p.out))"
                                                ;@db_indent 1 "f"
        result = f(p.in, p.out)                 ;@db_unindent 1
        close(p.in)                             ;@db 1 "  └─▶ 👍"
        close(p.out)

        # If cmd is still running try to kill it softly.
        if isalive(check(p))
            C.kill(p, C.SIGHUP)                           ;@db 3 "SIGHUP -> $p"
            wait(p; timeout=5.0)
            sent_sighup = true
        else
            sent_sighup = false
        end

        if !check_status
            @db return p, result
        elseif (didexit(check(p)) && p.exit_status == 0) || sent_sighup
            @db return result
        else
            throw(ProcessFailedException(p, cmd))
        end
    finally
        kill(p)
    end
end

function open(cmd::Cmd; kw...)
    c = Channel{Tuple{FD{Out},FD{In}}}(0)
    @asynclog "UnixIO.open(::Cmd)" open(cmd; kw...) do cmdin, cmdout
        put!(c, (cmdin, cmdout))
        @sync begin
            @async Base.wait_close(cmdin)
            @async Base.wait_close(cmdout)
        end
    end
    take!(c)
end

@doc README"""
### `UnixIO.read(::Cmd)` -- Read sub-process output.

    read(cmd::Cmd; [timeout=Inf,
                    check_status=true,
                    capture_stderr=false]) -> Vector{UInt8}
    read(cmd::Cmd, String; kw...) -> String

Run `cmd` using `fork` and `execv`.

Return byes written to stdout by `cmd`.

e.g.
```
julia> UnixIO.read(`uname -srm`, String)
"Darwin 20.3.0 x86_64\n"
```
"""
@db function read(cmd::Cmd, ::Type{String}; kw...)
    r = String(read(cmd; kw...))
    @db return r
end

@db function read(cmd::Cmd; timeout=Inf, kw...)
    r = open(cmd; kw...) do cmdin, cmdout
        shutdown(cmdin)
        Base.read(cmdout; timeout)
    end
    @db return r
end

# Sub Processes.

function Process(pid, infd, outfd)
    p = Process(pid, infd, outfd, false, nothing, nothing, nothing)
    @dblock processes_lock push!(processes, p)
    return p
end

Base.convert(::Type{C.pid_t}, p::Process) = p.pid

const processes_lock = Threads.SpinLock()
const processes = Set{Process}()

@doc README"""
### `UnixIO.waitpid` -- Wait for a sub-process to terminate.

    UnixIO.waitpid(pid) -> status

See [waitpid(3)](https://man7.org/linux/man-pages/man3/waitpid.3.html)
"""
@db function waitpid(p::Process; deadline::Float64=Inf)

    status = Ref{Cint}(0)
    delay = nothing

    while true
        r = C.waitpid(p.pid, status, C.WNOHANG | C.WUNTRACED)          ;@db 3 r
        if r == -1
            err = errno()
            if err == C.EINTR                                    ;@db 3 "EINTR"
                continue
            elseif err == C.ECHILD
                @db return nothing "ECHILD (No child process)"
            end
            systemerror(string("C.waitpid($(p.pid))"), err)
        end
        if r == p.pid
            s = status[]
            if C.WIFEXITED(s)
                p.exit_status = C.WEXITSTATUS(s)
            elseif C.WIFSTOPPED(s)
                p.stopped = true
                p.signal = WSTOPSIG(s)
            elseif C.WIFCONTINUED(s)
                p.stopped = false
                p.signal = nothing
                @assert p.exit_status == nothing
            elseif C.WIFSIGNALED(s)
                p.signal = C.WTERMSIG(s)
            else
                @error "Unhandedl termination status: $s" p
            end
            if !p.stopped
                @dblock processes_lock delete!(processes, p)
            end
            @db return nothing "terminated"
        end
        if time() >= deadline
            @db return nothing "timeout!"
        end

        if delay == nothing
            delay = exponential_delay()
        end
        t = popfirst!(delay)                                 ;@db 3 "sleep($t)"
        sleep(t)
    end
end

exponential_delay() =
    Iterators.Stateful(ExponentialBackOff(n=typemax(Int); factor=2))

code(i::C.siginfo_t) = i.si_code
pid(i::C.siginfo_t) = i._sifields._pad[1]
status(i::C.siginfo_t) = i._sifields._pad[3]

#=
`waitid(pidfd, ...)` only works if the `pidfd` is opened before the
process terminates.
The `waitpidfd` method below calls the normal `waitpid` function
after polling the `pidfd`.

"""
    waitid(::ReadFD{PidFD}) -> C.siginfo_t

See [waitid(2)](https://man7.org/linux/man-pages/man2/waitid.2.html)
"""
@db function waitid(fd::ReadFD{<:PidFD})
    si = Ref(C.siginfo_t())
    @assert pid(si[]) == 0
    @cerr0 C.waitid(C.P_PIDFD, fd, si, C.WEXITED | C.WNOHANG | C.WUNTRACED)
    @db return pid(si[]) == 0 ? nothing : si[]
end

@db function waitid(fd::ReadFD{<:PidFD}, p::Process)
    si = waitid(fd)
    if si == nothing
        return
    end
    @assert pid(si) == p.pid
    if code(si) == C.CLD_CONTINUED
        p.signal = nothing
        @assert p.exit_status == nothing
    else
        if code(si) == C.CLD_STOPPED
            p.stopped = true
            p.signal = status(si)
        elseif code(si) == C.CLD_CONTINUED
            p.stopped = false
            p.signal = nothing
        elseif code(si) == C.CLD_EXITED
            p.exit_status = status(si)
        else
            p.signal = status(si)
        end
    end
    if !p.stopped
        @dblock processes_lock delete!(processes, p)
    end
    nothing
end
=#

@db function waitpidfd(p::Process; deadline::Float64=Inf)

    waitpid(p; deadline=0.0)

    while deadline >= time() && isrunning(p)
        r = @cerr allow=C.ESRCH C.pifd_open(p.pid, 0)
        if r != -1
            fd = FD{In,PidFD}(r)
            fd.deadline = deadline
            try
                @dblock fd wait(fd)
            finally
                close(fd)
            end
        end
        waitpid(p; deadline=0.0)
    end

    nothing
end

@db function kill(p::Process)
    if isalive(check(p))
        C.close(p.in)
        C.close(p.out)
        C.kill(p, C.SIGKILL)                             ;@db 3 "SIGKILL -> $p"
        @asynclog "UnixIO.kill(::Process) -> waitpid()" wait(p)
    else
        @db "Already terminated!"
    end
end

@db 1 function kill_all_processes()
    @sync for p in processes                             ;@db 1 "SIGKILL -> $p"
        C.kill(p, C.SIGKILL)
        @async wait(p)
    end
end

struct ProcessFailedException <: Exception
    p::Process
    cmd::Cmd
end

function find_cmd_bin(cmd::Cmd)
    bin = Base.Sys.which(cmd.exec[1])
    bin != nothing || throw(ArgumentError("Command not found: $(cmd.exec[1])"))
    bin
end

@db 3 function pseudoterminal_spawn(cmd; kw...)

    # Create a Pseudoterminal to communicate with Child Process STDIN/OUT.
    parent_io, devpath = openpt()
    cmdin = parent_io.out
    cmdout = parent_io.in

    pid = spawn_process(cmd, devpath; kw...)

    process = Process(pid, cmdin, cmdout)
    set_extra(cmdout, :pt_client, process)
    return process
end

@db 3 function socketpair_spawn(cmd; capture_stderr=false, kw...)

    # Create a Unix Domain Socket to communicate with Child Process STDIN/OUT.
    parent_io, child_io = socketpair()
    cmdin = FD{Out}(parent_io)
    cmdout = FD{In}(C.dup(parent_io))
    @db 3 "dup($parent_io) -> $(cmdout.fd)"

    # Merge STDERR into STDOUT?
    # or leave connected to Parent Process STDERR?
    child_err = capture_stderr ? child_io : RawFD(Base.STDERR_NO)

    pid = spawn_process(cmd, child_io, child_io, child_err; kw...)

    # Close the child end of the socketpair.
    C.close(child_io)

    return Process(pid, cmdin, cmdout)
end

@db 3 function spawn_process(cmd, infd, outfd=infd, errfd=outfd; kw...)
    if true #Sys.isapple
        posix_spawn(cmd, infd, outfd, errfd; kw...)
    else
        fork_and_exec(cmd, infd, outfd, errfd; kw...)
    end
end

"""
    posix_spawn(cmd, in, out, err; [env=ENV]) -> pid

Run `cmd` using `posix_spawn`.
Connect child process (STDIN, STDOUT, STDERR) to (`in`, `out`, `err`).
See [posix_spawn(3)](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).
"""
@db function posix_spawn(cmd::Cmd, infd::Union{String,RawFD},
                                   outfd::Union{String,RawFD,Nothing}=nothing,
                                   errfd::Union{String,RawFD,Nothing}=nothing;
                                   env=nothing)
    @nospecialize

    # Find path to binary.
    cmd_bin = find_cmd_bin(cmd)

    # Prepare NULL-terminated vector of Argument Pointers.
    argv = [pointer.(cmd.exec); Ptr{UInt8}(0)]

    # Prepare NULL-terminated vector of Environment Variables.
    if env != nothing
        env_vector = ["$k=$v" for (k, v) in env]
    else
        env_vector = String[]
        i = 0
        while (s = @ccall jl_environ(i::Int32)::Any) != nothing
            push!(env_vector, s)
            i += 1
        end
    end
    envp = [pointer.(env_vector); Ptr{UInt8}(0)]

    # Allocate Attribute and File Action structs (destoryed in `finally`).
    pid = Ref{C.pid_t}()
    attr = Ref{C.posix_spawnattr_t}()
    actions = Ref{C.posix_spawn_file_actions_t}()
    @cerr0 C.posix_spawnattr_init(attr)
    @cerr0 C.posix_spawn_file_actions_init(actions)
    try
        # Set flags.
        @cerr0 C.posix_spawnattr_setflags(attr, C.POSIX_SPAWN_SETSIGDEF |
                                                C.POSIX_SPAWN_SETSIGMASK |
                                                C.POSIX_SPAWN_SETSID )

        # Set all signals to default behaviour.
        sigset = Ref{C.sigset_t}()
        @cerr0 C.sigfillset(sigset)
        @cerr0 C.sigdelset(sigset, C.SIGKILL)
        @cerr0 C.sigdelset(sigset, C.SIGSTOP)
        @cerr0 C.posix_spawnattr_setsigdefault(attr, sigset)

        # Un-mask all signals.
        emptyset = Ref{C.sigset_t}()
        @cerr0 C.sigemptyset(emptyset)
        @cerr0 C.posix_spawnattr_setsigmask(attr, emptyset)

        # Connect Child Process STDIN/OUT to socket.
        previous_stdno=-1
        for (fd, stdno) in ( infd => Base.STDIN_NO,
                            outfd => Base.STDOUT_NO,
                            errfd => Base.STDERR_NO)
            if fd isa String
                @db 4 "addopen($stdno, $fd)"
                @cerr C.posix_spawn_file_actions_addopen(
                    actions, stdno, fd, C.O_RDWR, 0)
            else
                @db 4 "adddup2($stdno, $fd, $stdno)"
                @cerr0 C.posix_spawn_file_actions_adddup2(
                    actions, something(fd, previous_stdno), stdno)
                previous_stdno = stdno
            end
        end

        # Start the Child Process
        GC.@preserve cmd env_vector begin
            @cerr0 C.posix_spawn(pid, cmd_bin, actions, attr, argv, envp)
        end
        @assert pid[] > 0

        @db return pid[]

    finally
        @cerr0 C.posix_spawn_file_actions_destroy(actions)
        @cerr0 C.posix_spawnattr_destroy(attr)
    end
end

"""
    fork_and_exec(cmd, in, out, err) -> pid

Run `cmd` using `fork` and `execv`.
Connect child process (STDIN, STDOUT, STDERR) to (`in`, `out`, `err`).
"""
@db function fork_and_exec(cmd::Cmd, infd::RawFD, outfd::RawFD, errfd::RawFD;
                           env=nothing)
    @nospecialize

    GC.@preserve cmd begin

        # Find path to binary.
        cmd_bin = find_cmd_bin(cmd)

        # Prepare arguments for `C.execv`.
        args = [pointer.(cmd.exec); Ptr{UInt8}(0)]

        # Mask all signals.
        oldmask = Ref{C.sigset_t}()
        newmask = Ref{C.sigset_t}()
        C.sigfillset(newmask)
        C.pthread_sigmask(C.SIG_SETMASK, newmask, oldmask);

        # Start Child Process ─────────────────────╮
        #                                          ▼
        pid = C.fork();                  if pid == 0

                                             # Set Default Signal Handlers.
                                             for n in 1:31
                                                 C.signal(n, C.SIG_DFL)
                                             end

                                             # Clear Signal Mask.
                                             emptyset = Ref{C.sigset_t}()
                                             C.sigemptyset(emptyset)
                                             C.pthread_sigmask(C.SIG_SETMASK,
                                                               emptyset,
                                                               C_NULL);

                                             # Connect STDIN/OUT to socket.
                                             C.dup2(infd,  Base.STDIN_NO)
                                             C.dup2(outfd, Base.STDOUT_NO)
                                             C.dup2(errfd, Base.STDERR_NO)

                                             # Execute command.
                                             C.execv(cmd_bin, args)
                                             C._exit(-1)
                                         end

        # Restore old signal mask.
        C.pthread_sigmask(C.SIG_SETMASK, oldmask, C_NULL);

        @assert pid > 0

        @db return pid
    end
end

# Pretty Printing.

type_icon(::Type{In})              = "→"
type_icon(::Type{Out})             = "←"
type_icon(::Type{S_IFIFO})         = "📥"
type_icon(::Type{S_IFCHR})         = "📞"
type_icon(::Type{S_IFDIR})         = "📂"
type_icon(::Type{S_IFBLK})         = "🧊"
type_icon(::Type{S_IFREG})         = "📄"
type_icon(::Type{S_IFLNK})         = "🔗"
type_icon(::Type{S_IFSOCK})        = "🧦"
type_icon(::Type{PidFD})           = "⚙ "
type_icon(::Type{Pseudoterminal})  = "⌨ "
type_icon(::Type{CanonicalMode})   = "📺"

function Base.show(io::IO, fd::FD{D,T}) where {D,T}
    fdint = convert(Cint, fd)
    t = type_icon(T)
    d = type_icon(D)
    print(io, "FD{$d$t}($fdint")
    fd.isclosed && print(io, "🚫")
    fd.nwaiting > 0 && print(io, repeat("👀", fd.nwaiting))
    islocked(fd) && print(io, "🔒")
    fd.timeout == Inf || print(io, ", ⏱", fd.timeout)
    if D == In
        fd.gothup && print(io, "☠ ")
        n = bytesavailable(fd.buffer)
        if n > 0
            print(io, ", $(n)📥")
        end
    end
    print(io, ")")
end

const signame = [string(unsafe_string(C.strsignal(n)),
                        " (", constant_name(n; prefix=r"^SIG[A-Z]+$",
                                              first=true), ")")
                 for n in 1:31]

function Base.show(io::IO, e::ProcessFailedException)
    p = e.p
    print(io,
          "ProcessFailedException: ", cmd, ", ", "pid ", p.pid,
          (waskilled(p) ? (" killed by ", signame[p.signal]) :
                          (" exit status ", p.exit_code))...)
end

dbtiny(fd::FD) = string(fd)

# Compiler hints.

include("precompile.jl")
_precompile_()

end # module UnixIO


text:
## Define a particle consisting of all cluster variables for a Binomial mixture
## Assumes N is 25
mutable struct BinomCluster
  n::Int                          # count of cluster members
  α::Array{Int}     # count of occurrence of levels
  β::Array{Int}           # The denominator in logprob calculation
  BinomCluster(dataFile::Matrix{Int}) = new(0,
                                              ones(Int, size(dataFile, 2)),
                                              ones(Int, size(dataFile, 2)))
end

function copy_particle(particle::BinomCluster, dataFile)
    new_particle = BinomCluster(dataFile)
    new_particle.n = particle.n
    for i in eachindex(particle.α)
      new_particle.α[i] = particle.α[i]
      new_particle.β[i] = particle.β[i]
    end
    return new_particle
end

function calc_logprob(obs::Array{Int}, cl::BinomCluster)
  out = 0.0
  for q in 1:length(obs)
    @inbounds out += - lgamma(obs[q] + 1) - lgamma(25 - obs[q] + 1) +
                    lgamma(cl.α[q] + obs[q]) + lgamma(25 - obs[q] + cl.β[q]) +
                    lgamma(cl.α[q] + cl.β[q]) -
                    lgamma(cl.α[q] + 25 + cl.β[q]) - lgamma(cl.α[q]) - lgamma(cl.β[q])
  end
  return out
end

function cluster_add!(cl::BinomCluster, obs::Array{Int})
  @inbounds cl.n  += Int(1)
  @inbounds cl.α  += obs
  @inbounds cl.β  += 25 .- obs
end


text:
@testset "Docs Function Tests" begin

  @test isdefined(Julz, :docs) == true

end


text:
@registerN  Krawchouk AbstractCDOP2 p 𝐍
export  Krawchouk

"""
     Krawchouk{p,𝐍}

Also spelled  Krawtchouk,  Kravhcuk,….

References: [Koekoek and Swarttouw §1.10](https://arxiv.org/pdf/math/9602214.pdf);  see  also  [Coleman](https://arxiv.org/pdf/1101.1798.pdf) for a different  parameterization.
"""
Krawchouk

abcde(::Type{<:Krawchouk{p,𝐍}})  where {p,𝐍} = NamedTuple{(:a,:b,:c,:d,:e)}((0, p-1, 0, 1, -p*𝐍))

basis_symbol(::Type{<:Krawchouk{p,𝐍}}) where {p,𝐍} = "kᵖ" * "₍" * sprint(io -> unicode_subscript(io, 𝐍)) * "₎"
Polynomials.domain(::Type{<:Krawchouk{p,𝐍}}) where {p,𝐍} = Polynomials.Interval(0, 𝐍)
weight_function(::Type{<:Krawchouk{p,𝐍}})  where {p,𝐍} = x  -> generalized_binomial(𝐍,x)  * p^x * (1-p)^(𝐍-x)

function k1k0(P::Type{<:Krawchouk}, n::Int)
    one(eltype(P))/(n+1)
end

function classical_hypergeometric(P::Type{<:Krawchouk{p,N}}, n::Int, x) where {p,N}
    monic = classical_hypergeometric(MonicKrawchouk{p,N},n,x)
    monic * kn(P,n)
end

##
## --------------------------------------------------
##

@registerN MonicKrawchouk AbstractCDOP2 p 𝐍
export MonicKrawchouk
ϟ(::Type{<:MonicKrawchouk{p,𝐍}}) where {p,𝐍} = Krawchouk{p,𝐍}
ϟ(::Type{<:MonicKrawchouk{p,𝐍,T}}) where {p,𝐍,T} = Krawchouk{p,𝐍,T}
@register_monic(MonicKrawchouk)

function classical_hypergeometric(P::Type{<:MonicKrawchouk{p,N}}, n::Int, x) where {p,N}
    Pochhammer( -N,n) * p^n * pFq((-n, -x), -N, 1/p)
end