text:
function make_fqparser( filename; forcegzip=false )
   fopen = open( filename, "r" )
   if isgzipped( filename ) || forcegzip
      to_open = ZlibInflateInputStream( fopen, reset_on_end=true )
   else
      to_open = BufferedInputStream( fopen )
   end
   FASTQ.Reader( to_open, fill_ambiguous=DNA_A ), Requests.ResponseStream{TCPSocket}()
end

# modified for Bio v0.2 with tryread_bool!
@inline function read_chunk!( chunk, parser )
   i = 1
   while i <= length(chunk) && !eof(parser)
      read!( parser, chunk[i] )
      i += 1
   end
   while i <= length(chunk)
      pop!(chunk) # clean up if we are at the end
   end
   parser
end

function make_http_fqparser( url::String; forcegzip=false )
   response = Requests.get_streaming(url)
   if isgzipped( url ) || forcegzip
      zlibstr  = ZlibInflateInputStream( response.buffer, reset_on_end=true )
      fqparser = FASTQ.Reader( zlibstr, fill_ambiguous=DNA_A )
   else
      fqparser = FASTQ.Reader( response.buffer, fill_ambiguous=DNA_A )
   end
   fqparser, response
end

# Use this version to parse reads from a parser that is reliant on the state
function read_http_chunk!( chunk, parser, resp; maxtime=24 )
   i = 1
   iobuf      = resp.buffer
   nb_needed  = 8192
   start_mark = iobuf.mark
   start_size = iobuf.size
   start_time = time()
   if !(200 <= resp.response.status < 300)
      error("HTTP Code $(resp.response.status)! Download failed!")
   end
   while i <= length(chunk) && !(eof(iobuf) && eof(parser))
      if resp.state!=Requests.BodyDone && nb_available(iobuf) < nb_needed
         sleep(eps(Float64))
         if time() - start_time > maxtime
            error("HTTP Timeout! Unable to download file!")
         end
         continue
      end
      read!( parser, chunk[i] )
      i += 1
   end
   while i <= length(chunk)
      pop!(chunk) # clean up if we are at the end
   end
   parser
end

function allocate_chunk( parser; size=10000 )
  chunk = Vector{eltype(parser)}( size )
  for i in 1:length(chunk)
     chunk[i] = eltype(parser)()
  end
  chunk
end

function allocate_fastq_records( size::Int=10000 )
   chunk = Vector{FASTQRecord}( size )
   for i in 1:length(chunk)
      chunk[i] = FASTQRecord()
   end
   chunk
end

function process_reads!( parser, param::AlignParam, lib::GraphLib, quant::GraphLibQuant,
                         multi::MultiMapping{SGAlignSingle}, mod::B;
                         bufsize=150, sam=false, qualoffset=33,
                         response=Requests.ResponseStream{TCPSocket}(),
                         http=false ) where B <: BiasModel

   reads  = allocate_fastq_records( bufsize )
   mean_readlen = 0.0
   total        = 0
   mapped       = 0
   if sam
      stdbuf = BufferedOutputStream( STDOUT )
      write_sam_header( stdbuf, lib )
   end
   while length(reads) > 0
      if http
         read_http_chunk!( reads, parser, response )
      else
         read_chunk!( reads, parser )
      end
      total += length(reads)
      @inbounds for i in 1:length(reads)
         fill!( reads[i], qualoffset )
         align = ungapped_align( param, lib, reads[i] )
         if !isnull( align )
            biasval = count!( mod, reads[i].sequence )
            if length( align.value ) > 1
               push!( multi, align.value, biasval, quant, lib )
               sam && write_sam( stdbuf, reads[i], align.value, lib, qualoffset=qualoffset )
            else
               count!( quant, align.value[1], biasval )
               sam && write_sam( stdbuf, reads[i], align.value[1], lib, qualoffset=qualoffset )
            end
            mapped += 1
            @fastmath mean_readlen += (length(reads[i].sequence) - mean_readlen) / mapped
         end
      end
      if total % 100000 == 0
         gc()
      end
   end # end while
   if sam
      close(stdbuf)
   end
   mapped,total,mean_readlen
end

function process_paired_reads!( fwd_parser, rev_parser, param::AlignParam,
                                lib::GraphLib, quant::GraphLibQuant,
                                multi::MultiMapping{SGAlignPaired}, mod::B;
                                bufsize=50, sam=false, qualoffset=33,
                                     response=Requests.ResponseStream{TCPSocket}(),
                                mate_response=Requests.ResponseStream{TCPSocket}(),
                                http=false ) where B <: BiasModel

   fwd_reads  = allocate_fastq_records( bufsize )
   rev_reads  = allocate_fastq_records( bufsize )
   mean_readlen = 0.0
   total        = 0
   mapped       = 0
   if sam
      stdbuf = BufferedOutputStream( STDOUT )
      write_sam_header( stdbuf, lib )
   end
   while length(fwd_reads) > 0 && length(rev_reads) > 0
      if http
         read_http_chunk!( fwd_reads, fwd_parser, response )
         read_http_chunk!( rev_reads, rev_parser, mate_response )
      else
         read_chunk!( fwd_reads, fwd_parser )
         read_chunk!( rev_reads, rev_parser )
      end
      total += length(fwd_reads)
      @inbounds for i in 1:length(fwd_reads)
         fill!( fwd_reads[i], qualoffset )
         fill!( rev_reads[i], qualoffset )
         fwd_aln,rev_aln = ungapped_align( param, lib, fwd_reads[i], rev_reads[i] )
         if !isnull( fwd_aln ) && !isnull( rev_aln )
            biasval = count!( mod, fwd_reads[i].sequence, rev_reads[i].sequence )
            if length( fwd_aln.value ) > 1
               push!( multi, fwd_aln.value, rev_aln.value, biasval, quant, lib )
               sam && write_sam( stdbuf, fwd_reads[i], rev_reads[i], fwd_aln.value, rev_aln.value, lib,
                                 paired=true, is_pair_rc=param.is_pair_rc, qualoffset=qualoffset )
            else
               count!( quant, fwd_aln.value[1], rev_aln.value[1], biasval )
               sam && write_sam( stdbuf, fwd_reads[i], fwd_aln.value[1], lib,
                                 paired=true, fwd_mate=true, is_pair_rc=param.is_pair_rc,
                                 qualoffset=qualoffset )
               sam && write_sam( stdbuf, rev_reads[i], rev_aln.value[1], lib,
                                 paired=true, fwd_mate=false, is_pair_rc=param.is_pair_rc,
                                 qualoffset=qualoffset )
            end
            mapped += 1
            @fastmath mean_readlen += (length(fwd_reads[i].sequence) - mean_readlen) / mapped
         end
      end
   end # end while
   if sam
      close(stdbuf)
   end
   mapped,total,mean_readlen
end


text:
module test_bukdu_changeset

using Test
using Bukdu # Assoc Changeset
using Bukdu.HTML5.Form # change

struct User
    name
    age::Int
    salary::Float64
end

changeset = Changeset(User)
@test changeset.changes == NamedTuple()

params = Assoc("user_name" => "Alex", "user_age" => "20")
changeset = change(User, params)
@test changeset.changes == (name="Alex", age=20)

params = Assoc("user_" => "Alex", "user_age" => "20")
changeset = change(User, params)
@test changeset.changes == (age=20,)

params = Assoc("user_id" => "1", "user_name" => "Shen", "user_age" => "20")
changeset = change(Changeset(User, (id=1, name="Alex", age=20)), params, primary_key="id")
@test changeset.changes == (id=1, name="Shen")

params = Assoc("user_id" => "1", "user_name" => "Shen", "user_age" => "20")
changeset = change(Changeset(User, (id=1, name="Alex", age=20)), params)
@test changeset.changes == (name="Shen",)

params = Assoc("user_id" => "1", "user_name" => "Shen", "user_age" => "20", "user_city" => "Paris")
changeset = change(Changeset(User, (id=1, name="Alex", age=20, city="Seoul")), params)
@test changeset.changes == (name="Shen", city="Paris")

params = Assoc("user_id" => "1", "user_name" => "Shen", "user_age" => "20", "user_city" => "Paris")
changeset = change(Changeset(User, (id=1, name="Alex", age=20, city="Paris")), params)
@test changeset.changes == (name="Shen",)

changeset = Changeset(User, (name="",))
params = Assoc("user_name" => "Shen", "user_age" => "20")
changeset = change(changeset, params)
@test changeset.changes == (name="Shen",)

changeset = Changeset(User, (id=1, name="",))
params = Assoc("user_id" => "1", "user_name" => "Shen", "user_age" => "20")
changeset = change(changeset, params, primary_key="id")
@test changeset.changes == (id=1, name="Shen",)

changeset = Changeset(User, (id=1, name="",))
params = Assoc("user_id" => "1", "user_name" => "Shen", "user_age" => "20")
changeset = change(changeset, params)
@test changeset.changes == (name="Shen",)

end # module test_bukdu_changeset

ext: jl


text:
module Utils

using ConcurrentUtils

function poll_until(f)
    for _ in 1:1000
        f() && return true
        sleep(0.01)
    end
    return false
end

# 1 tick is about 30 ns
function unfair_sleep(nticks)
    for _ in 1:nticks
        spinloop()
    end
end

end  # module


text:
# Define abstract interface
"""
$(TYPEDEF)

Abstract supertype for magnetization fields.
"""
abstract type Magnetizations{T} end

"""
$(SIGNATURES)

Return the magnetization field M [T/μ₀] (μ₀ being the vacuum permeability)
as `SVector{3,T}` for a given magnetic field H [T/μ₀], location r [m] and
time t [s].
"""
function magnetizationField(magnetization::Magnetizations{T},H::SVector{3,T},r::SVector{3,T},t::T) where {T}
    error("magnetizationField is not implemented for magnetization::$(typeof(magnetization)).")
end

"""
$(SIGNATURES)

Return if (`Bool`) magnetization field is zero at a given location r [m] at all
times.
"""
function isZero(magnetization::Magnetizations{T},r::SVector{3,T}) where {T}
    error("isZero is not implemented for magnetization::$(typeof(magnetization)).")
end

"""
$(SIGNATURES)

Return `QuadratureNodes` required to integrate over the magnetization M.
"""
function QuadratureNodes(magnetization::Magnetizations{T},density) where {T}
    error("QuadratureNodes is not implemented for magnetization::$(typeof(magnetization)).")
end

# Define specific subtypes
## separable magnetization
struct SeparableMagnetization{T,U<:TracerDistributions{T},V<:MagneticMoments{T}} <: Magnetizations{T}
    tracerDistribution::U
    magneticMoment::V
end

@inline isZero(magnetization::SeparableMagnetization,r::SVector{3,T}) where {T} = isZero(magnetization.tracerDistribution,r)

function magnetizationField(magnetization::SeparableMagnetization{T},H::SVector{3,T},r::SVector{3,T},t::T) where {T}
    return tracerConcentration(magnetization.tracerDistribution,r,t)*meanMagneticMoment(magnetization.magneticMoment,H,r,t)
end

function QuadratureNodes(magnetization::SeparableMagnetization{T},density=10/1e-3) where {T}
    return QuadratureNodes(magnetization.tracerDistribution,density)
end


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

mutable struct StorageSkuListResult <: SwaggerModel
    value::Any # spec type: Union{ Nothing, Vector{Sku} } # spec name: value

    function StorageSkuListResult(;value=nothing)
        o = new()
        validate_property(StorageSkuListResult, Symbol("value"), value)
        setfield!(o, Symbol("value"), value)
        o
    end
end # type StorageSkuListResult

const _property_map_StorageSkuListResult = Dict{Symbol,Symbol}(Symbol("value")=>Symbol("value"))
const _property_types_StorageSkuListResult = Dict{Symbol,String}(Symbol("value")=>"Vector{Sku}")
Base.propertynames(::Type{ StorageSkuListResult }) = collect(keys(_property_map_StorageSkuListResult))
Swagger.property_type(::Type{ StorageSkuListResult }, name::Symbol) = Union{Nothing,eval(Meta.parse(_property_types_StorageSkuListResult[name]))}
Swagger.field_name(::Type{ StorageSkuListResult }, property_name::Symbol) =  _property_map_StorageSkuListResult[property_name]

function check_required(o::StorageSkuListResult)
    true
end

function validate_property(::Type{ StorageSkuListResult }, name::Symbol, val)
end


text:
using ColorTypes, FixedPointNumbers
using Test

@testset "RGB accessors" begin

    # This also checks that the constructor order is the same, even if the
    # storage order is not (e.g. RGB vs BGR)
    Crgb = filter(T -> T <: AbstractRGB, ColorTypes.parametric3)
    @testset "accessors for $C and its transparent color" for C in Crgb
        rgb = C(1, 0.5, 0)
        @test alpha(rgb) === 1.0
        @test red(rgb)   === rgb.r === 1.0
        @test green(rgb) === rgb.g === 0.5
        @test blue(rgb)  === rgb.b === 0.0

        argb = alphacolor(C)(1, 0.5, 0, 0.8)
        rgba = coloralpha(C)(1, 0.5, 0, 0.8)

        @test alpha(argb) === argb.alpha === 0.8
        @test red(argb)   === argb.r === 1.0
        @test green(argb) === argb.g === 0.5
        @test blue(argb)  === argb.b === 0.0

        @test alpha(rgba) === rgba.alpha === 0.8
        @test red(rgba)   === rgba.r === 1.0
        @test green(rgba) === rgba.g === 0.5
        @test blue(rgba)  === rgba.b === 0.0
    end
    @testset "accessors for RGB24 and ARGB32" begin
        rgb24 = RGB24(1, 0.5, 0)
        @test alpha(rgb24) === 1.0N0f8
        @test red(rgb24)   === 1.0N0f8
        @test green(rgb24) === 0.5N0f8
        @test blue(rgb24)  === 0.0N0f8

        argb32 = ARGB32(1, 0.5, 0, 0.8)
        @test alpha(argb32) === 0.8N0f8
        @test red(argb32)   === 1.0N0f8
        @test green(argb32) === 0.5N0f8
        @test blue(argb32)  === 0.0N0f8
    end

    @test_throws MethodError red(HSV(100, 0.6, 0.4))
end

@testset "alpha" begin
    @test alpha(RGB24(0.2, 0.3, 0.4)) === 1N0f8
    @test alpha(ARGB32(0.2, 0.3, 0.4, 0.8)) === 0.8N0f8
    @test alpha(HSV(100, 0.4, 0.6)) === 1.0
    @test alpha(HSVA(100, 0.4, 0.6, 0.8)) === 0.8
    @test alpha(AHSV{Float32}(100, 0.4, 0.6, 0.8)) === 0.8f0
end

@testset "gray" begin
    @test gray(Gray(0.2)) === 0.2
    @test gray(GrayA(0.2, 0.8)) === 0.2
    @test gray(AGray{Float32}(0.2, 0.8)) === 0.2f0
    @test gray(Gray24(0.2)) === N0f8(0.2)
    @test gray(AGray32(0.2, 0.8)) === N0f8(0.2)
    @test gray(Gray{Bool}(1)) === true
    @test gray(Gray{Bool}(0)) === false

    @testset "gray for Real" begin
        @test gray(1) === 1
        @test gray(0.8) === 0.8
        @test gray(0.8N0f8) === 0.8N0f8
        @test gray(true) === true
        @test gray(false) === false
    end
    @test_throws MethodError gray(HSVA(100, 0.4, 0.6, 0.8))
end

@testset "chroma" begin
    for C in [Lab, DIN99, DIN99o, DIN99d, Luv]
        @test chroma(C(60, -40, 30)) ≈ 50.0
    end
    @test chroma(LCHab(60, 40, 30)) ≈ 40.0
    @test chroma(LCHuv(60, 40, 30)) ≈ 40.0
    @test chroma(LabA(60, -40, 30, 0.5)) ≈ 50.0
    @test chroma(ALab(60, -40, 30, 0.5)) ≈ 50.0
    @inferred chroma(LCHab(60, 40, 30))
    @inferred chroma(LCHuv(6f1, 4f1, 3f1))
    @inferred chroma(LabA(60, -40, 30, 0.5))
    @test_throws MethodError chroma(HSV(30, 0.4, 0.6))
end

@testset "hue" begin
    @test hue(HSV(30, 0.4, 0.6)) ≈ 30.0
    @test hue(HSL(30, 0.4, 0.6)) ≈ 30.0
    @test hue(HSI(30, 0.4, 0.6)) ≈ 30.0
    for C in [Lab, DIN99, DIN99o, DIN99d, Luv]
        @test hue(C(60, -30, 30)) ≈ 135.0
    end
    @test hue(LCHab(60, 40, 30)) ≈ 30.0
    @test hue(LCHuv(60, 40, 30)) ≈ 30.0
    @test hue(LabA(60, -30, 30, 0.5)) ≈ 135.0
    @test hue(ALab(60, -30, 30, 0.5)) ≈ 135.0
    @inferred hue(LCHab(60, -30, 30))
    @inferred hue(LCHuv(6f1, -3f1, 3f1))
    @inferred hue(LabA(60, -30, 30, 0.5))
    @test hue(HSV(999, 0.4, 0.6)) == 999 # without normalization
end

@testset "color" begin
    @test color(RGB(1, 0.5, 0)) === RGB{Float64}(1, 0.5, 0)
    @test color(RGBA{N0f8}(1, 0.5, 0, 0.8)) === RGB{N0f8}(1, 0.5, 0)
    @test color(ARGB{Float32}(1, 0.5, 0, 0.8)) === RGB{Float32}(1, 0.5, 0)
    @test color(RGB24(1, 0.5, 0)) === RGB24(1, 0.5, 0)
    @test color(ARGB32(1, 0.5, 0, 0.8)) === RGB24(1, 0.5, 0)

    @test color(HSV(100, 0.4, 0.6)) === HSV{Float64}(100, 0.4, 0.6)
    @test color(HSVA(100, 0.4, 0.6, 0.8)) === HSV{Float64}(100, 0.4, 0.6)
    @test color(AHSV{Float32}(100, 0.4, 0.6, 0.8)) === HSV{Float32}(100, 0.4, 0.6)

    @test color(Gray(0.2)) === Gray{Float64}(0.2)
    @test color(GrayA{N0f8}(0.2, 0.8)) === Gray{N0f8}(0.2)
    @test color(AGray{Float32}(0.2, 0.8)) === Gray{Float32}(0.2)
    @test color(Gray24(0.2)) === Gray24(0.2)
    @test color(AGray32(0.2, 0.8)) === Gray24(0.2)
end

@testset "to_top" begin
    @test ColorTypes.to_top(RGB{N0f8}) === Colorant{N0f8,3}
    @test ColorTypes.to_top(RGB24) === Colorant{N0f8,3}
    @test ColorTypes.to_top(Gray24) === Colorant{N0f8,1}
    @test ColorTypes.to_top(HSVA{Float32}) === Colorant{Float32,4}
    @test_throws MethodError ColorTypes.to_top(RGB)

    # for instances
    @test ColorTypes.to_top(RGB(1, 0.5, 0)) === Colorant{Float64,3}
    @test ColorTypes.to_top(AGray32(.8)) === Colorant{N0f8,2}
end

@testset "length" begin
    @test length(RGB) == 3
    @test length(XRGB) == 3
    @test length(ARGB) == 4
    @test length(RGB24) == 3
    @test length(ARGB32) == 4
    @test length(Gray) == 1
    @test length(Gray24) == 1
    @test length(AGray32) == 2
    @test length(AGray{Float32}) == 2

    @test length(ARGB(1.0,0.8,0.6,0.4)) == 4
end

@testset "eltype" begin
    @test @inferred(eltype(Color{N0f8})) === N0f8
    @test @inferred(eltype(RGB{Float32})) === Float32
    @test @inferred(eltype(RGBA{Float64})) === Float64
    @test @inferred(eltype(RGB24)) === N0f8
    # @test eltype(RGB) == TypeVar(:T, Fractional)
    @inferred(eltype(RGB))      # just test that it doesn't error

    # for instances
    @test @inferred(eltype(RGB{N0f8}(1,0,0))) === N0f8
    @test @inferred(eltype(RGB(0x01,0x00,0x00))) === N0f8
    @test @inferred(eltype(RGB(1.0,0,0))) === Float64
    @test @inferred(eltype(ARGB(1.0,0.8,0.6,0.4))) === Float64
    @test @inferred(eltype(RGBA{Float32}(1.0,0.8,0.6,0.4))) === Float32
    @test @inferred(eltype(RGB24(1,0.5,0))) === N0f8
    @test @inferred(eltype(ARGB32(1,0.5,0,0.8))) === N0f8

    @test @inferred(eltype(Gray(1))) === N0f8
    @test @inferred(eltype(Gray(1.0))) === Float64
    @test @inferred(eltype(Gray24(0.8))) === N0f8
    @test @inferred(eltype(AGray32(0.8))) === N0f8

    @test @inferred(eltype(HSV(30,1,0))) === Float32
    @test @inferred(eltype(HSV(30,1.0,0.0))) === Float64

    # eltypes_supported
    @test N0f8 <: ColorTypes.eltypes_supported(RGB(1,0,0))
end

@testset "color_type" begin
    @test @inferred(color_type(RGB)) === RGB
    @test @inferred(color_type(RGBA)) === RGB
    @test @inferred(color_type(ARGB)) === RGB
    @test @inferred(color_type(RGB{N0f8})) === RGB{N0f8}
    @test @inferred(color_type(RGBA{Float32})) === RGB{Float32}
    @test @inferred(color_type(ARGB{Float64})) === RGB{Float64}
    @test @inferred(color_type(RGB24)) === RGB24
    @test @inferred(color_type(ARGB32)) === RGB24

    @test @inferred(color_type(BGR)) === BGR
    @test @inferred(color_type(XRGB)) === XRGB
    @test @inferred(color_type(RGBX)) === RGBX

    @test @inferred(color_type(Gray)) === Gray
    @test @inferred(color_type(GrayA)) == Gray{<:ColorTypes.Fractional} # FIXME
    @test @inferred(color_type(AGray)) == Gray{<:ColorTypes.Fractional} # FIXME
    @test_broken @inferred(color_type(GrayA)) === Gray
    @test_broken @inferred(color_type(AGray)) === Gray
    @test @inferred(color_type(Gray{N0f8})) === Gray{N0f8}
    @test @inferred(color_type(GrayA{Float32})) === Gray{Float32}
    @test @inferred(color_type(AGray{Float64})) === Gray{Float64}
    @test @inferred(color_type(Gray24)) === Gray24
    @test @inferred(color_type(AGray32)) === Gray24

    @test @inferred(color_type(HSV)) === HSV
    @test @inferred(color_type(HSVA)) === HSV
    @test @inferred(color_type(AHSV)) === HSV
    @test @inferred(color_type(HSV{Float16})) === HSV{Float16}
    @test @inferred(color_type(HSVA{Float32})) === HSV{Float32}
    @test @inferred(color_type(AHSV{Float64})) === HSV{Float64}

    @test @inferred(color_type(AbstractRGB)) === AbstractRGB
    @test @inferred(color_type(AbstractRGBA)) === AbstractRGB
    @test @inferred(color_type(AbstractARGB)) === AbstractRGB
    @test @inferred(color_type(AbstractRGB{N0f8})) === AbstractRGB{N0f8}
    @test @inferred(color_type(AbstractRGBA{RGB,Float32})) === RGB
    @test @inferred(color_type(AbstractARGB{RGB{Float64},Float64})) === RGB{Float64}
    @test @inferred(color_type(TransparentRGB)) === AbstractRGB

    @test @inferred(color_type(AbstractGrayA)) === AbstractGray
    @test @inferred(color_type(AbstractAGray)) === AbstractGray
    @test @inferred(color_type(TransparentGray)) === AbstractGray

    @test @inferred(color_type(Color3)) === Color3
    @test @inferred(color_type(Transparent3)) === Color3
    @test @inferred(color_type(Color)) === Color
    @test @inferred(color_type(TransparentColor)) === Color
    @test @inferred(color_type(TransparentColor{RGB})) === RGB
    @test @inferred(color_type(TransparentColor{RGB,Float64})) === RGB
    @test @inferred(color_type(TransparentColor{RGB{Float64},Float64})) === RGB{Float64}
    @test_throws MethodError color_type(Colorant{N0f8})

    @test_throws MethodError color_type(N0f8) === Gray{N0f8}

    # for instances
    @test @inferred(color_type(RGB{N0f8}(1,0,0))) === RGB{N0f8}
    @test @inferred(color_type(ARGB(1.0,0.8,0.6,0.4))) === RGB{Float64}
    @test @inferred(color_type(RGBA{Float32}(1.0,0.8,0.6,0.4))) === RGB{Float32}
    @test_throws MethodError color_type(1N0f8) === 1N0f8
end

@testset "base_color_type" begin
    @test @inferred(base_color_type(RGB)) === RGB
    @test @inferred(base_color_type(RGBA)) === RGB
    @test @inferred(base_color_type(ARGB)) === RGB
    @test @inferred(base_color_type(RGB{N0f8})) === RGB
    @test @inferred(base_color_type(RGBA{Float32})) === RGB
    @test @inferred(base_color_type(ARGB{Float64})) === RGB
    @test @inferred(base_color_type(RGB24)) === RGB24
    @test @inferred(base_color_type(ARGB32)) === RGB24

    @test @inferred(base_color_type(BGR)) === BGR
    @test @inferred(base_color_type(BGR{N0f8})) === BGR
    @test @inferred(base_color_type(XRGB)) === XRGB
    @test @inferred(base_color_type(RGBX)) === RGBX

    @test @inferred(base_color_type(Gray)) === Gray
    @test @inferred(base_color_type(GrayA)) === Gray
    @test @inferred(base_color_type(AGray)) === Gray
    @test @inferred(base_color_type(Gray{N0f8})) === Gray
    @test @inferred(base_color_type(GrayA{Float32})) === Gray
    @test @inferred(base_color_type(AGray{Float64})) === Gray
    @test @inferred(base_color_type(Gray24)) === Gray24
    @test @inferred(base_color_type(AGray32)) === Gray24

    @test @inferred(base_color_type(HSV)) === HSV
    @test @inferred(base_color_type(HSVA)) === HSV
    @test @inferred(base_color_type(AHSV)) === HSV
    @test @inferred(base_color_type(HSV{Float16})) === HSV
    @test @inferred(base_color_type(HSVA{Float32})) === HSV
    @test @inferred(base_color_type(AHSV{Float64})) === HSV

    @test @inferred(base_color_type(AbstractRGB)) === AbstractRGB
    @test @inferred(base_color_type(AbstractRGBA)) === AbstractRGB
    @test @inferred(base_color_type(AbstractARGB)) === AbstractRGB
    @test @inferred(base_color_type(AbstractRGB{N0f8})) === AbstractRGB
    @test @inferred(base_color_type(AbstractRGBA{RGB,Float32})) === RGB
    @test @inferred(base_color_type(AbstractARGB{RGB{Float64},Float64})) === RGB
    @test @inferred(base_color_type(TransparentRGB)) === AbstractRGB

    @test_broken @inferred(base_color_type(AbstractGrayA)) === AbstractGray
    @test_broken @inferred(base_color_type(AbstractAGray)) === AbstractGray
    @test_broken @inferred(base_color_type(TransparentGray)) === AbstractGray

    @test_broken @inferred(base_color_type(Color3)) === Color3
    @test_broken @inferred(base_color_type(Transparent3)) === Color3
    @test @inferred(base_color_type(Color)) === Color
    @test @inferred(base_color_type(TransparentColor)) === Color
    @test @inferred(base_color_type(TransparentColor{RGB})) === RGB
    @test @inferred(base_color_type(TransparentColor{RGB,Float64})) === RGB
    @test @inferred(base_color_type(TransparentColor{RGB{Float64},Float64})) === RGB
    @test_throws MethodError color_type(Colorant{N0f8})

    @test_broken base_color_type(N0f8) === Gray

    # for instances
    @test @inferred(base_color_type(RGB{N0f8}(1,0,0))) === RGB
    @test @inferred(base_color_type(ARGB(1.0,0.8,0.6,0.4))) === RGB
    @test @inferred(base_color_type(RGBA{Float32}(1.0,0.8,0.6,0.4))) === RGB
    @test @inferred(base_color_type(1N0f8)) === Gray
end

@testset "base_colorant_type" begin
    @test @inferred(base_colorant_type(RGB)) === RGB
    @test @inferred(base_colorant_type(RGBA)) === RGBA
    @test @inferred(base_colorant_type(ARGB)) === ARGB
    @test @inferred(base_colorant_type(RGB{N0f8})) === RGB
    @test @inferred(base_colorant_type(RGBA{Float32})) === RGBA
    @test @inferred(base_colorant_type(ARGB{Float64})) === ARGB
    @test @inferred(base_colorant_type(RGB24)) === RGB24
    @test @inferred(base_colorant_type(ARGB32)) === ARGB32

    @test @inferred(base_colorant_type(BGR)) === BGR
    @test @inferred(base_colorant_type(BGR{N0f8})) === BGR
    @test @inferred(base_colorant_type(XRGB)) === XRGB
    @test @inferred(base_colorant_type(RGBX)) === RGBX

    @test @inferred(base_colorant_type(Gray)) === Gray
    @test @inferred(base_colorant_type(GrayA)) === GrayA
    @test @inferred(base_colorant_type(AGray)) === AGray
    @test @inferred(base_colorant_type(Gray{N0f8})) === Gray
    @test @inferred(base_colorant_type(GrayA{Float32})) === GrayA
    @test @inferred(base_colorant_type(AGray{Float64})) === AGray
    @test @inferred(base_colorant_type(Gray24)) === Gray24
    @test @inferred(base_colorant_type(AGray32)) === AGray32

    @test @inferred(base_colorant_type(HSV)) === HSV
    @test @inferred(base_colorant_type(HSVA)) === HSVA
    @test @inferred(base_colorant_type(AHSV)) === AHSV
    @test @inferred(base_colorant_type(HSV{Float16})) === HSV
    @test @inferred(base_colorant_type(HSVA{Float32})) === HSVA
    @test @inferred(base_colorant_type(AHSV{Float64})) === AHSV

    @test @inferred(base_colorant_type(AbstractRGB)) === AbstractRGB
    @test @inferred(base_colorant_type(AbstractRGBA)) === ColorAlpha # FIXME
    @test @inferred(base_colorant_type(AbstractARGB)) === AlphaColor # FIXME
    @test_broken @inferred(base_colorant_type(AbstractRGBA)) === AbstractRGBA
    @test_broken @inferred(base_colorant_type(AbstractARGB)) === AbstractARGB
    @test @inferred(base_colorant_type(AbstractRGB{N0f8})) === AbstractRGB
    @test @inferred(base_colorant_type(AbstractRGBA{RGB,Float32})) === ColorAlpha # FIXME
    @test @inferred(base_colorant_type(AbstractARGB{RGB{Float64},Float64})) === AlphaColor # FIXME
    @test_broken @inferred(base_colorant_type(AbstractRGBA{RGB,Float32})) === RGBA
    @test_broken @inferred(base_colorant_type(AbstractARGB{RGB{Float64},Float64})) === ARGB
    @test @inferred(base_colorant_type(TransparentRGB)) === TransparentColor # FIXME
    @test_broken @inferred(base_colorant_type(TransparentRGB)) === TransparentRGB

    @test_broken @inferred(base_colorant_type(AbstractGrayA)) === AbstractGrayA
    @test_broken @inferred(base_colorant_type(AbstractAGray)) === AbstractAGray
    @test_broken @inferred(base_colorant_type(TransparentGray)) === TransparentGray

    @test_broken @inferred(base_colorant_type(Color3)) === Color3
    @test_broken @inferred(base_colorant_type(Transparent3)) === Transparent3
    @test @inferred(base_colorant_type(Color)) === Color
    @test @inferred(base_colorant_type(TransparentColor)) === TransparentColor
    @test @inferred(base_colorant_type(TransparentColor{RGB})) === TransparentColor # FIXME
    @test @inferred(base_colorant_type(TransparentColor{RGB,Float64})) === TransparentColor # FIXME
    @test @inferred(base_colorant_type(TransparentColor{RGB{Float64},Float64})) === TransparentColor # FIXME
    @test_broken @inferred(base_colorant_type(TransparentColor{RGB})) === TransparentRGB
    @test_broken @inferred(base_colorant_type(TransparentColor{RGB,Float64})) === TransparentRGB
    @test_broken @inferred(base_colorant_type(TransparentColor{RGB{Float64},Float64})) === TransparentRGB
    @test_throws MethodError color_type(Colorant{N0f8})

    @test_broken @inferred(base_colorant_type(N0f8)) === Gray

    # for instances
    @test @inferred(base_colorant_type(RGB{N0f8}(1,0,0))) === RGB
    @test @inferred(base_colorant_type(ARGB(1.0,0.8,0.6,0.4))) === ARGB
    @test @inferred(base_colorant_type(RGBA{Float32}(1.0,0.8,0.6,0.4))) === RGBA
    @test_broken @inferred(base_colorant_type(1N0f8)) === Gray
end

@testset "floattype" begin
    @test @inferred(floattype(RGBA{Float32})) === RGBA{Float32}
    @test @inferred(floattype(BGR{N0f8})    ) === BGR{Float32}
    @test @inferred(floattype(Gray{N0f8})   ) === Gray{Float32}
    @test @inferred(floattype(Gray{Bool})   ) === Gray{Float32}
    @test @inferred(floattype(N0f8)         ) === Float32
    @test @inferred(floattype(Bool)         ) === Float32
    @test @inferred(floattype(Float32)      ) === Float32
    @test @inferred(floattype(Float64)      ) === Float64

    @test @inferred(floattype(ARGB32)) == ARGB{Float32}
    @test @inferred(floattype(AGray32)) == AGray{Float32}
    @test @inferred(floattype(RGB24)) == RGB{Float32}
    @test @inferred(floattype(Gray24)) == Gray{Float32}

    @test_throws MethodError @inferred(floattype(RGB))

    @test_throws MethodError @inferred(floattype(RGB{N0f8}(1,0,0)))
end

# TODO: move `colorant_string` to `show.jl`
@testset "colorant_string" begin
    @test ColorTypes.colorant_string(Union{}) == "Union{}"
    @test ColorTypes.colorant_string(RGB{N0f8}) == "RGB"
    @test ColorTypes.colorant_string(HSV{Float32}) == "HSV"
    @test ColorTypes.colorant_string(RGB24) == "RGB24"
    @test ColorTypes.colorant_string(ARGB32) == "ARGB32"
    @test ColorTypes.colorant_string(Gray24) == "Gray24"
    @test ColorTypes.colorant_string(AGray32) == "AGray32"
    @test ColorTypes.colorant_string(RGB) == "RGB"
    @test_throws MethodError ColorTypes.colorant_string(Float32)
end

# TODO: move `colorant_string_with_eltype` to `show.jl`
@testset "colorant_string_with_eltype" begin
    @test ColorTypes.colorant_string_with_eltype(Union{}) == "Union{}"
    @test ColorTypes.colorant_string_with_eltype(RGB{N0f8}) == "RGB{N0f8}"
    @test ColorTypes.colorant_string_with_eltype(HSV{Float32}) == "HSV{Float32}"
    @test ColorTypes.colorant_string_with_eltype(RGB24) == "RGB24"
    @test ColorTypes.colorant_string_with_eltype(ARGB32) == "ARGB32"
    @test ColorTypes.colorant_string_with_eltype(Gray24) == "Gray24"
    @test ColorTypes.colorant_string_with_eltype(AGray32) == "AGray32"
    @test_broken ColorTypes.colorant_string_with_eltype(RGB) != "RGB{Any}" # TODO: define the appropriate expression
    @test ColorTypes.colorant_string_with_eltype(RGB{Union{}}) == "RGB{Union{}}"
    @test_throws MethodError ColorTypes.colorant_string_with_eltype(Float32)
end

@testset "ccolor" begin
    @test @inferred(ccolor(Colorant, XRGB{N0f8})) === XRGB{N0f8}
    @test @inferred(ccolor(Colorant{N0f8}, RGBX{Float32})) === RGBX{N0f8}
    @test @inferred(ccolor(Colorant{N0f8,3}, BGR{N0f8})) === BGR{N0f8}

    @test @inferred(ccolor(AbstractRGB, HSV{Float32})) === AbstractRGB{Float32} # is not concrete type
    @test @inferred(ccolor(AbstractRGB{N0f8}, HSV{Float32})) === AbstractRGB{N0f8} # is not concrete type
    @test @inferred(ccolor(AbstractRGB, RGB24)) === RGB24
    @test_broken @inferred(ccolor(AbstractRGB{Float32}, RGB24))

    @test @inferred(ccolor(RGB, RGB)) == RGB # with symbol `S` instead of `T`
    @test @inferred(ccolor(RGB, HSV)) == RGB # with symbol `S` instead of `T`
    @test @inferred(ccolor(Gray, Gray)) == Gray # with symbol `S` instead of `T`
    @test @inferred(ccolor(Gray, HSV)) == Gray # with symbol `S` instead of `T`

    @test @inferred(ccolor(RGB{Float32}, HSV{Float32})) === RGB{Float32}
    @test @inferred(ccolor(RGB{N0f8}, HSV{Float32})) === RGB{N0f8}
    @test @inferred(ccolor(RGB, HSV{Float32})) === RGB{Float32}

    @test @inferred(ccolor(ARGB{Float32}, HSV{Float32})) === ARGB{Float32}
    @test @inferred(ccolor(ARGB{N0f8}, HSV{Float32})) === ARGB{N0f8}
    @test @inferred(ccolor(ARGB, HSV{Float32})) === ARGB{Float32}

    @test @inferred(ccolor(RGB{Float32}, AHSV{Float32})) === RGB{Float32}
    @test @inferred(ccolor(RGB{N0f8}, AHSV{Float32})) === RGB{N0f8}
    @test @inferred(ccolor(RGB, AHSV{Float32})) === RGB{Float32}

    @test @inferred(ccolor(RGBA{Float32}, AHSV{Float32})) === RGBA{Float32}
    @test @inferred(ccolor(RGBA{N0f8}, AHSV{Float32})) === RGBA{N0f8}
    @test @inferred(ccolor(RGBA, AHSV{Float32})) === RGBA{Float32}

    @test @inferred(ccolor(HSV{Float32}, RGB{N0f8})) === HSV{Float32}
    @test @inferred(ccolor(HSV, RGB{N0f8})) === HSV{Float32}

    @test @inferred(ccolor(Gray{N0f8}, Bool)) === Gray{N0f8}
    @test @inferred(ccolor(Gray, Bool)) === Gray{Bool}
    @test @inferred(ccolor(Gray, Int)) === Gray{N0f8}
    @test @inferred(ccolor(Gray, Float32)) === Gray{Float32}

    @test @inferred(ccolor(RGB24, HSV{Float32})) === RGB24
    @test @inferred(ccolor(ARGB32, HSV{Float32})) === ARGB32
    @test @inferred(ccolor(Gray24, HSV{Float32})) === Gray24
    @test @inferred(ccolor(AGray32, HSV{Float32})) === AGray32

    @test @inferred(ccolor(RGB{N0f8}, RGB24)) === RGB{N0f8}
    @test @inferred(ccolor(ARGB{N0f8}, ARGB32)) === ARGB{N0f8}
    @test @inferred(ccolor(Gray{N0f8}, Gray24)) === Gray{N0f8}
    @test @inferred(ccolor(AGray{N0f8}, AGray32)) === AGray{N0f8}

    @test @inferred(ccolor(TransparentColor, AHSV{Float32})) === AHSV{Float32}

    # Ambiguous storage order, choose AlphaColor or ColorAlpha
    @test_throws ErrorException ccolor(TransparentColor, HSV{Float32})
    @test_throws ErrorException ccolor(TransparentColor{RGB}, HSV{Float32})
    @test_throws ErrorException ccolor(TransparentColor{RGB,Float64}, HSV{Float32})
    @test_throws ErrorException ccolor(TransparentColor{RGB{Float64},Float64}, HSV{Float32})
    @test_throws ErrorException ccolor(TransparentColor{RGB{Float64},Float64,4}, HSV{Float32})

    @test @inferred(ccolor(AlphaColor, RGB)) === ARGB
    @test_broken @inferred(ccolor(AlphaColor, RGB{N0f8})) === ARGB{N0f8}
    @test @inferred(ccolor(AlphaColor, RGB24)) === ARGB32
    @test_broken @inferred(ccolor(AbstractARGB, RGB)) === ARGB
    @test_broken @inferred(ccolor(AbstractARGB, RGB{N0f8})) === ARGB{N0f8}
    @test_broken @inferred(ccolor(AbstractARGB, RGB24)) === ARGB32

    for C in filter(T -> T <: AbstractRGB, ColorTypes.parametric3)
        @test @inferred(ccolor(RGB24, C)) === RGB24
        @test @inferred(ccolor(ARGB32, C)) === ARGB32
        @test @inferred(ccolor(Gray24, C)) === Gray24
        @test @inferred(ccolor(AGray32, C)) === AGray32
    end

    @test_throws MethodError ccolor(RGB, RGB{N0f8}(1,0,0))
end

# TODO: move `reinterpret` to `conversions.jl`
@testset "reinterpret" begin
    @test reinterpret(UInt32, RGB24()) === 0x00000000
    @test reinterpret(UInt32, ARGB32()) === 0xff000000
    @test reinterpret(UInt32, Gray24()) === 0x00000000
    @test reinterpret(UInt32, AGray32()) === 0xff000000

    @test reinterpret(UInt32, RGB24(0.071,0.204,0.337)) === 0x00123456
    @test reinterpret(UInt32, ARGB32(0.071,0.204,0.337,0.671)) === 0xab123456
    @test reinterpret(UInt32, Gray24(0.071)) === 0x00121212
    @test reinterpret(UInt32, AGray32(0.071, 0.671)) === 0xab121212

    @test reinterpret(RGB24, 0x00123456) === RGB24(0.071,0.204,0.337)
    @test reinterpret(RGB24, 0xdc123456) ==  RGB24(0.071,0.204,0.337)
    @test reinterpret(RGB24, 0x00123456) !== reinterpret(RGB24, 0xdc123456)
    @test reinterpret(UInt32, reinterpret(RGB24, 0xdc123456)) === 0xdc123456

    @test reinterpret(ARGB32, 0xab123456) === ARGB32(0.071,0.204,0.337,0.671)

    @test reinterpret(Gray24, 0x00121212) === Gray24(0.071)
    @test reinterpret(Gray24, 0xdc121212) ==  Gray24(0.071)
    @test reinterpret(Gray24, 0x00121212) !== reinterpret(Gray24, 0xdc121212)
    @test reinterpret(UInt32, reinterpret(Gray24, 0xdc121212)) === 0xdc121212
    # the following behavior is undefined, so this is just for the regression test.
    @test reinterpret(Gray24, 0xdc00ff12) ==  Gray24(0.071)
    @test reinterpret(UInt32, reinterpret(Gray24, 0xdc00ff12)) === 0xdc00ff12

    @test reinterpret(AGray32, 0xab121212) === AGray32(0.071, 0.671)

    @test_throws MethodError reinterpret(UInt32, ARGB{N0f8}())
    @test_throws ErrorException reinterpret(ARGB{N0f8}, 0x12345678)
end

@testset "comparisons" begin
    Cp3 = ColorTypes.parametric3
    for C in unique(vcat(Cp3, coloralpha.(Cp3), alphacolor.(Cp3)))
        @test C{Float64}(1,0,0) == C{Float32}(1,0,0)
        @test C{Float32}(1,0,0) != C{Float32}(1,0,0.1)
    end

    for (a, b) in ((Gray(1.0), Gray(1)),
                   (GrayA(0.8, 0.6), AGray(0.8, 0.6)),
                   (RGB(1, 0.5, 0), BGR(1, 0.5, 0)),
                   (RGBA(1, 0.5, 0, 0.8), ABGR(1, 0.5, 0, 0.8)),
                   (Gray(0.8N0f8), Gray24(0.8)))
        local a, b
        @test a !== b
        @test a == b
        @test hash(a) == hash(b)
    end
    for (a, b) in ((RGB(1, 0.5, 0), RGBA(1, 0.5, 0, 0.9)),
                   (RGB(0.5, 0.5, 0.5), Gray(0.5)),
                   (HSV(0, 0, 0.5), HSV(100, 0, 0.5)), # grays
                   (Lab(70, 0, 60), LCHab(70, 60, 90)))
        local a, b
        @test a != b
        @test hash(a) != hash(b)
    end
    # It's not obvious whether we want these to compare as equal, but
    # whatever happens, you want hashing and equality-testing to yield the
    # same result
    for (a, b) in ((RGB(1, 0.5, 0), RGBA(1, 0.5, 0, 1)),
                   (HSV(100, 0.4, 0.6), AHSV(100, 0.4, 0.6, 1)))
        local a, b
        @test (a == b) == (hash(a) == hash(b))
    end
end

@testset "BigFloat comparisons" begin
    # issue #52
    @test AGray{BigFloat}(0.5,0.25) == AGray{BigFloat}(0.5,0.25)
    @test RGBA{BigFloat}(0.5, 0.25, 0.5, 0.5) == RGBA{BigFloat}(0.5, 0.25, 0.5, 0.5)
end

@testset "isapprox" begin
    @test Gray(0.8) ≈ Gray(0.8)
    @test Gray(0.8) ≈ Gray(0.8 + eps())
    @test Gray(0.8) ≈ Gray(0.8 - eps())
    @test !(Gray(0.8) ≈ Gray(0.80000002))
    @test Gray(0.8f0) ≈ Gray(0.80000002)
    @test Gray(0.8N0f8) ≈ Gray24(0.8)

    @test Gray(0.8) ≈ 0.8 + eps()
    @test 0.8 + eps() ≈ Gray(0.8)

    @test GrayA(0.8, 0.4) ≈ GrayA(0.8 + eps(), 0.4)
    @test GrayA(0.8, 0.4) ≈ GrayA(0.8, 0.4 + eps())
    @test GrayA(0.8, 0.4) ≈ GrayA(0.8 + eps(), 0.4 + eps())

    @test RGB(0.2, 0.8, 0.4) ≈ RGB(0.2, 0.8 + eps(), 0.4)
    @test RGBA(0.2, 0.8, 0.4, 0.2) ≈ RGBA(0.2, 0.8 + eps(), 0.4, 0.2 - eps())

    c_n0f8 = RGB{N0f8}(0.2, 0.69, 0.4)
    c_f32 = RGB{Float32}(0.2, 0.69, 0.4)
    @test c_n0f8 != c_f32 && c_n0f8 ≈ c_f32

    c1 = RGB(0.2, 0.8, 0.4)
    c2 = RGB(0.2, 0.7, 0.4)
    @test c1 ≈ c2 atol=0.11
    @test !isapprox(c1, c2; atol=0.09)
    @test c1 ≈ RGBX(0.2, 0.8, 0.4)
    @test !(c1 ≈ HSV(140.0f0,0.75f0,0.8f0))  # the latter comes from convert when using Colors
    @test !(HSV(0,0,0.5) ≈ HSV(100,0,0.5))
    @test HSVA(100,0.4,0.6,0.8) ≈ HSVA{Float32}(100,0.4,0.6,0.8)
end

@testset "identities for Gray" begin
    @test oneunit(Gray{N0f8}) === Gray{N0f8}(1)
    @test zero(Gray{N0f8}) === Gray{N0f8}(0)
    @test oneunit(Gray) === Gray{N0f8}(1)
    @test zero(Gray) === Gray{N0f8}(0)

    @test_throws MethodError oneunit(Gray24)
    @test_throws MethodError zero(Gray24)
    @test_throws MethodError oneunit(AGray32)
    @test_throws MethodError zero(AGray32)
    @test_throws MethodError oneunit(AGray{N0f8})
    @test_throws MethodError zero(AGray{N0f8})
    @test_throws MethodError oneunit(GrayA{Float32})
    @test_throws MethodError zero(GrayA{Float32})

    @test_throws MethodError oneunit(RGB{N0f8})
    @test_throws MethodError zero(RGB{N0f8})

    g = Gray{Float32}(0.8)
    @test_throws MethodError oneunit(g)
    @test_throws MethodError zero(g)

    @test_broken one(Gray{Float32}) * g == g * one(Gray{Float32}) == g
end


text:
key_input = Signal(Key, nokey)

function main(window)
    cont = container(40em, 40em) |> fillcolor("#f1f3f1")
    box = inset(Escher.middle, cont, vbox(md"**Focus and press w, a, s, d or arrow keys**", key_input))

    keypress("w a s d up down left right", box) >>> key_input
end


text:
using LightGraphs, PkgAuthentication, GraphPlot, Cairo, Compose

file = joinpath(@__DIR__, "src/PkgAuthentication.jl")

g = SimpleDiGraph()
lines = readlines(file)

vertices = string.(nameof.(subtypes(PkgAuthentication.State)))
for vertex in vertices
    add_vertex!(g)
end

for line in lines
    m = match(r"^function step\(state::(.+?)\)::Union{(.+?)}$", line)
    if m !== nothing
        for target in strip.(split(m[2], ','))
            add_edge!(g, findfirst(==(m[1]), vertices), findfirst(==(target), vertices))
        end
    end
end
plot = gplot(g, nodelabel=vertices, linetype="curve")
draw(PNG(joinpath(@__DIR__, "structure.png"), 16cm, 16cm), plot)


text:
module Nektar

include("nekton.jl")

end # module


text:
module IsoPkg

using Pkg

export @iso

const DEFAULT_GROUP=string("v",VERSION.major,".",VERSION.minor)

"""Name of the current package group"""
const GROUP=Ref(DEFAULT_GROUP)

function env_path()
    depots=Pkg.depots1()
    pos="env_isolated_packages"
    p=joinpath(Pkg.depots1(),pos,GROUP[])
    isdir(p) || mkpath(p)
    return p
end

"""
    switch(group_name::String)
    switch()

Switch the current package group to `group_name`. If not given, switch to the default group name(the Julia version string).
"""
function switch(group_name::String=DEFAULT_GROUP)
    if group_name=="" || occursin(r"^\.*$|[/\\]",group_name)
        error("\"$group_name\" is invalid name")
    else
        GROUP[]=group_name
    end
    return nothing
end

#Search if pkg exists in the registry
function search_registry(pkg::AbstractString)
    for registry in Pkg.Types.collect_registries()
        data = Pkg.Types.read_registry(joinpath(registry.path, "Registry.toml"))
        for (_uuid, pkgdata) in data["packages"]
            if pkg==pkgdata["name"]
                return true
            end
        end
    end
    return false
end

function str2spec(pkg::String)
    if '@' in pkg
        name,ver=string.(split(pkg,'@'))
        ver=string(VersionNumber(ver))
        p=Pkg.PackageSpec(name=name,version=ver)
    else
        name,ver=pkg,""
        p=Pkg.PackageSpec(name=pkg)
    end
    return (name=name,ver=ver,spec=p)
end

#Search if `pkg` is installed. If installed, return (pkg, name, ver, path), otherwise raise an error.
function search_pkg(pkg::String)
    name,ver=str2spec(pkg)
    found=String[]
    env=env_path()
    for n in readdir(env)
        if n==pkg
            return (pkg=pkg,name=name,ver=ver,path=joinpath(env,pkg))
        elseif !in('@',pkg) && startswith(n,pkg*"@")
            push!(found,n)
        end
    end
    if length(found)==1
        name,ver=str2spec(found[1])
        return (pkg=found[1],name=name,ver=ver,path=joinpath(env,found[1]))
    elseif length(found)>1
        error("Found multiple packages: $found. Please specify an exact name.")
    else
        if search_registry(name)
            error("\"$pkg\" is not installed. Use IsoPkg.add(\"$pkg\") to install it.")
        else
            error("\"$name\" is invalid (not found in registry).")
        end
    end
end

#Get detailed information about `pkg` according to Project.toml and Manifest.toml
function pkg_info(pkg::AbstractString)
    path=joinpath(env_path(),pkg)
    name=uuid=ver=""
    pinned=false
    deps=String[]

    try
        p=Pkg.TOML.parsefile(joinpath(path,"Project.toml"))
        name,uuid=first(p["deps"])
    catch
    end

    if name!=""
        try
            m=Pkg.TOML.parsefile(joinpath(path,"Manifest.toml"))[name][1]
            ver=get(m,"version",ver)
            deps=get(m,"deps",deps)
            pinned=get(m,"pinned",pinned)
        catch
        end
    end
    return (name=name,uuid=uuid,ver=ver,deps=deps,pinned=pinned)
end

function activate(pkg::AbstractString)
    env=env_path()
    if pkg in readdir(env)
        Pkg.activate(joinpath(env,pkg))
    elseif occursin(r"^\.*$|[/\\]",pkg)
        error("\"$pkg\" is invalid name")
    else
        @info "New \"$pkg\""
        Pkg.activate(joinpath(env,pkg))
    end
end

"""
    @iso(using_or_import_statement)
    @iso(using_or_import_statement, version_string)

Load a package or a package with a specified version. Currently, it does not support loading multiple packages at once.

# Examples

    #Load Glob (after `IsoPkg.add("Glob")`)
    @iso using Glob

    #Load UnicodePlots v1.2.0 (after `IsoPkg.add("UnicodePlots@1.2.0")`)
    @iso using UnicodePlots "1.2.0"

-----------------------------------------------

    @iso(pkg_name, statement)

Run `statement` in the `pkg_name` environment.

# Examples

    #Load Glob v1.2.0 (after `IsoPkg.add("Glob@1.2.0")`; equivalent to `@iso using Glob "1.2.0"`
    @iso "Glob@1.2.0" using Glob

    #Test Glob
    @iso "Glob@1.2.0" Pkg.test("Glob")

    #Show Glob status (equivalent to `IsoPkg.status("Glob@1.2.0")`)
    @iso "Glob@1.2.0" pkg"status --manifest"
"""
macro iso(expr1,expr2="")
    if typeof(expr1)==Expr && (expr1.head==:import || expr1.head==:using)
        if length(expr1.args)>1
            @error "@iso currently does not support $(expr1.head) multiple modules"
            return nothing
        elseif expr1.args[1].head==:(.)
            if expr1.args[1].args[1]!=:(.)
                name=string(expr1.args[1].args[1])
            else
                @error "@iso cannot be used with relative-$(expr1.head) qualifiers"
                return nothing
            end
        elseif expr1.args[1].head==:(:)  && expr1.args[1].args[1].head==:(.)
            if expr1.args[1].args[1].args[1]!=:(.)
                name=string(expr1.args[1].args[1].args[1])
            else
                @error "@iso cannot be used with relative-$(expr1.head) qualifiers"
                return nothing
            end
        else
            @error "@iso should not be used with \"$expr1\""
            return nothing
        end
        return quote
            n=$name
            ver=$(esc(expr2))
            pkg = search_pkg(ver=="" ? n : n*"@"*ver).pkg
            @iso pkg $(esc(expr1))
        end
    else
        return quote
            pkg=$(esc(expr1))
            if !(typeof(pkg)<:AbstractString)
                error("@iso should not be used with \"$($(string(expr1)))\"")
            end
            cur_proj=dirname(Pkg.Types.find_project_file())
            try
                activate(pkg)
                $(esc(expr2))
            finally
                Pkg.activate(cur_proj)
            end
        end
    end
end

"""
    add(pkg::AbstractString)

Install a package.

If `pkg` is in the "name@ver" form, then the specified version will be added and pinned.

# Examples

    #install Glob
    IsoPkg.add("Glob")

    #install Glob v1.2.0 and pin the version
    IsoPkg.add("Glob@1.2.0")
"""
function add(pkg::AbstractString)
    name,ver,spec=str2spec(pkg)
    if search_registry(name)
        if ver==""
            @iso name Pkg.add(spec)
        else
            pkg=name*"@"*ver
            @iso pkg begin
                Pkg.add(spec)
                if !pkg_info(pkg).pinned
                    Pkg.pin(spec)
                end
            end
        end
    else
        @error "\"$name\" not found in registry"
    end
    return nothing
end

"""
    rm(pkg::AbstractString)

Remove a package.
"""
function rm(pkg::AbstractString)
    Base.rm(search_pkg(pkg).path; recursive=true)
    return nothing
end

"""
    update(;force=false)

Update all packages. The packages which names include version (such as name@ver) will NOT be updated. This will help the scripts which rely on this kind of packages not break.

If really want to update all installed package, set `force` to true. (Be careful)
"""
function update(; force=false)
    for pkg in readdir(env_path())
        if force || !('@' in pkg)
            try
                @iso pkg Pkg.update()
            catch
                @info "Update \"$pkg\" error."
            end
        end
    end
    return nothing
end

"""
    update(pkg::AbstractString)

Upgrade a package. The package which name includes version (such as name@ver) will NOT be updated except `force` is true.
"""
function update(pkg::AbstractString; force=false)
    if force || !('@' in pkg)
        @iso search_pkg(pkg).pkg Pkg.update()
    end
    return nothing
end

"""
    status()

Show the status all installed packages.
"""
function status()
    for pkg in readdir(env_path())
        p=pkg_info(pkg)
        u = p.uuid=="" ? ' '^8 : p.uuid[1:8]
        u = "[$u] "
        v = p.ver=="" ? "" : "v"*p.ver
        pin = p.pinned ? " ⚲" : ""
        printstyled(u;color=:light_black); printstyled(pkg;color=:light_yellow)
        s=""
        if pkg==p.name
            s*=" ($v$pin)"
        elseif pkg!=p.name*"@"*p.ver
            s*=" ($(p.name) - $v$pin)"
        else
            s*=pin
        end
        println(s)
    end
    return nothing
end

"""
    status(pkg::AbstractString)

Show the status of a package.
"""
function status(pkg::AbstractString)
    @iso search_pkg(pkg).pkg Pkg.status(mode=PKGMODE_MANIFEST)
    return nothing
end

"""
    pin(pkg::AbstractString)

Pin the package version and change its name to match version if possible.
"""
function pin(pkg::AbstractString)
    env=env_path()
    pkgs=readdir(env)
    pkg,name,ver,path=search_pkg(pkg)
    p=pkg_info(pkg)
    newpkg=p.name*"@"*p.ver
    if p.ver!=""
        if p.name==name && p.ver!=ver && !(newpkg in pkgs)
            p.pinned || @iso pkg Pkg.pin(p.name)
            mv(path,joinpath(env,newpkg))
        else
            if p.pinned
                println("\"$pkg\" is already pinned")
            else
                @iso pkg Pkg.pin(p.name)
            end
        end
    else
        error("Cannot get the version of \"$pkg\"")
    end
    return nothing
end

"""
    free(pkg::AbstractString)

Free the package version and remove the version in name if possible.
"""
function free(pkg::AbstractString)
    env=env_path()
    pkgs=readdir(env)
    pkg,name,ver,path=search_pkg(pkg)
    p=pkg_info(pkg)
    newpkg=p.name
    if p.ver!=""
        if p.name==name && ver!="" && !(newpkg in pkgs)
            p.pinned && @iso pkg Pkg.free(p.name)
            mv(path,joinpath(env,newpkg))
        else
            if p.pinned
                @iso pkg Pkg.free(p.name)
            else
                println("\"$pkg\" is already freed")
            end
        end
    else
        error("Cannot get the version of \"$pkg\"")
    end
    return nothing
end

"""
    show_not_newest_pkgs()

Return the packages which is not the newest version in current environment.

Ref: https://www.juliabloggers.com/understanding-package-version-restrictions-in-julia
"""
function show_not_newest_pkgs()
    cd(joinpath(DEPOT_PATH[1], "registries", "General")) do
       deps = Pkg.dependencies()
       registry = Pkg.TOML.parse(read("Registry.toml", String))
       general_pkgs = registry["packages"]

       constrained = Dict{String, Tuple{VersionNumber,VersionNumber}}()
       for (uuid, dep) in deps
           suuid = string(uuid)
           dep.is_direct_dep || continue
           dep.version === nothing && continue
           haskey(general_pkgs, suuid) || continue
           pkg_meta = general_pkgs[suuid]
           pkg_path = joinpath(pkg_meta["path"], "Versions.toml")
           versions = Pkg.TOML.parse(read(pkg_path, String))
           newest = maximum(VersionNumber.(keys(versions)))
           if newest > dep.version
               constrained[dep.name] = (dep.version, newest)
           end
       end
       return constrained
    end
end

end


text:
using Documenter, TensorNetworkAD

makedocs(;
    modules=[TensorNetworkAD],
    format=Documenter.HTML(),
    pages=[
        "Home" => "index.md",
        "User Guide" => "userguide.md",
        "Doc-Strings" => "docstrings.md",
        "Future Directions" => "future.md"
    ],
    repo="https://github.com/under-Peter/TensorNetworkAD.jl/blob/{commit}{path}#L{line}",
    sitename="TensorNetworkAD.jl",
    authors="Andreas Peter",
)

deploydocs(;
    repo="github.com/under-Peter/TensorNetworkAD.jl",
)