text:
module DropboxSDK

# using Base.Iterators
using ConfParser
using HTTP
using JSON
using SHA

"""
    geteuid()::Int

Call the Unix `geteuid` function.
"""
geteuid()::Int = ccall(:geteuid, Cint, ())

"""
    mapget(fun::Function, dict::Dict, key, def=nothing)

Get an entry from a dictionary, and apply the function `fun` to the
result. If the key `key` is missing from the dictionary, return the
default value `def`.
"""
function mapget(fun::Function, dict::Dict, key, def=nothing)
    value = get(dict, key, nothing)
    if value === nothing return def end
    fun(value)
end

export start_task
"""
    start_task(fun::Function)::Task

Create a new task from `fun` and schedule the task, similar to the
`@task` macro. Also register a hook that shows exception and backtrace
if the task fails.
"""
function start_task(fun::Function, info=nothing)::Task
    function wrap()
        try
            fun()
        catch ex
            println("Caught exception in task")
            if info !== nothing
                println("Context: ", string(info))
            end
            println("Exception: ", string(ex)[1:min(10000, end)])
            println("Backtrace: ", stacktrace(catch_backtrace()))
            rethrow()
        end
    end
    schedule(Task(wrap))
end

include("types.jl")

################################################################################

export Authorization
"""
    struct Authorization
        access_token::String
    end

Contains an access token. Almost all Dropbox API functions require
such a token. Access tokens are like passwords and should be treated
with the same care.
"""
struct Authorization
    access_token::String
end

export get_authorization
"""
    get_authorization()::Authorization

Get an authorization token. This function first looks for an
environment values `DROPBOXSDK_ACCESS_TOKEN`, and then for a file
`secrets.http` in the current directory. If neither exists, this is an
error.
"""
function get_authorization()::Authorization
    access_token = nothing
    if access_token === nothing
        access_token = get(ENV, "DROPBOXSDK_ACCESS_TOKEN", nothing)
    end
    if access_token === nothing
        try
            # Ensure that the file containing the access token is a
            # simple file, is owned by the user, and is only
            # accessible by the user
            filename = joinpath(homedir(), ".dropboxsdk.http")
            st = stat(filename)
            if ispath(st)
                if !isfile(st)
                    println("Configuration file \"$filename\" exists,",
                            " but is not a file")
                elseif st.uid != geteuid()
                    println("Configuration file \"$filename\" exists,",
                            " but is not owned by the current effective user")
                elseif filemode(st) & 0o077 != 0
                    println("Configuration file \"$filename\" exists,",
                            " but is accessible by others")
                else
                    conf = ConfParse(filename)
                    parse_conf!(conf)
                    access_token = retrieve(conf, "access_token")
                end
            end
        catch
        end
    end
    if access_token === nothing
        error("Could not find access token for Dropbox")
    end
    Authorization(access_token)
end

################################################################################

const try_after = Ref(time())
function set_retry_delay(retry_after::Real)
    @assert retry_after >= 0
    next_try = time() + retry_after
    try_after[] = max(try_after[], next_try)
end
function wait_as_requested()
    delay = try_after[] - time()
    if delay > 0
        println("Info: Waiting $(round(delay, digits=1)) seconds...")
        sleep(delay)
    end
end

# TODO: experiment with retry_non_idempotent = true
# TODO: optionally set file time stamp
function post_http(auth::Authorization,
                   url::String,
                   args::Union{Nothing, Dict} = nothing;
                   content = HTTP.nobody,
                   expecting_content::Bool = false,
                   verbose::Int = 0
                   )::Tuple{Dict, Vector{UInt8}}
    headers = ["Authorization" => "Bearer $(auth.access_token)"]
    if args !== nothing
        json_args = JSON.json(args)
    else
        json_args = nothing
    end

    # Are we sending arguments as body or in a header?
    if content === HTTP.nobody && !expecting_content
        # Arguments as body
        if json_args !== nothing
            push!(headers, "Content-Type" => "application/json")
            body = json_args
        else
            body = HTTP.nobody
        end
    else
        # Arguments in header
        if json_args !== nothing
            push!(headers, "Dropbox-API-Arg" => json_args)
        end
        push!(headers, "Content-Type" => "application/octet-stream")
        body = content
    end

    result = nothing
    result_content = HTTP.nobody

    retry_count = 1
    @label retry

    wait_as_requested()

    try

        response = HTTP.request("POST", url, headers, body;
                                canonicalize_headers=true, verbose=verbose)

        # Are we expecting the result in the body or in a header?
        if !expecting_content
            # Result as body
            json_result = response.body
            result_content = HTTP.nobody
        else
            # Result in header
            json_result = Dict(response.headers)["Dropbox-Api-Result"]
            result_content = response.body
        end

        result = JSON.parse(String(json_result); dicttype=Dict, inttype=Int64)
        if result === nothing
            result = Dict()
        end

    catch exception
        if exception isa HTTP.StatusError
            response = exception.response
            result = try
                JSON.parse(String(response.body); dicttype=Dict, inttype=Int64)
            catch
                Dict("error_summary" =>
                     "No JSON result in HTTP error: $response")
            end
            result["http_status"] = string(exception.status)
            error_summary = get(result, "error_summary",
                                "(no error summary in HTTP error): $response")

            # Should we retry?
            retry_after = mapget(s->parse(Float64, s),
                                 Dict(response.headers), "Retry-After")
            if retry_after !== nothing
                println("Info: Warning $(exception.status): $error_summary")
                set_retry_delay(retry_after)
                @goto retry
            end

            # If this is a real error (e.g. a file does not exist),
            # report in right away without retrying
            if (exception.status in (400, 401, 403, 409) &&
                get(result, ".tag", nothing) != "internal_error")

                throw(DropboxError(result))
            end

            # Too many weird things can go wrong. We will thus retry
            # for any error. If the error goes away, we don't really
            # care. If it persists, we'll give up after several
            # retries.
            println("Info: Error $exception")
            if retry_count >= 3
                println("Info: Giving up after attempt #$retry_count.")
                # The error was properly diagnosed and reported by
                # Dropbox
                throw(DropboxError(result))
            end
            retry_count += 1
            println("Info: Retrying, attempt #$retry_count...")
            sleep(1)
            @goto retry
        end

        # Too many weird things can go wrong. We will thus retry for
        # any error. If the error goes away, we don't really care. If
        # it persists, we'll give up after several retries.
        println("Info: Error $exception")
        if retry_count >= 3
            println("Info: Giving up after attempt #$retry_count.")
            # This is probably not an error in Dropbox
            rethrow()
        end
        retry_count += 1
        println("Info: Retrying, attempt #$retry_count...")
        sleep(1)
        @goto retry

        # elseif (exception isa ArgumentError &&
        #         (exception.msg ==
        #          "`unsafe_write` requires `iswritable(::SSLContext)`"))
        #     # I don't understand this error; maybe it is ephemeral? We
        #     # will retry.
        # elseif (exception isa ErrorException &&
        #         startswith(exception.msg,
        #                    "Unexpected end of input\nLine: 0\n"))
        #     # This is a JSON parsing error. Something went wrong with
        #     # Dropbox's response. We will retry.
        # elseif exception isa Base.IOError
        #     # I don't understand this error; maybe it is ephemeral? We
        #     # will retry.
        # elseif exception isa HTTP.IOExtras.IOError
        #     # I don't understand this error; maybe it is ephemeral? We
        #     # will retry.
    end

    # The request worked -- return
    return result, result_content
end

"""
    post_rpc(auth::Authorization,
             fun::String,
             args::Union{Nothing, Dict} = nothing
            )::Dict

Post an RPC request to the Dropbox API.
"""
function post_rpc(auth::Authorization,
                  fun::String,
                  args::Union{Nothing, Dict} = nothing)::Dict
    post_http(auth, "https://api.dropboxapi.com/2/$fun", args)[1]
end

"""
    post_content_upload(auth::Authorization,
                        fun::String,
                        args::Union{Nothing, Dict} = nothing
                       )::Dict

Post a Content Upload request to the Dropbox API.
"""
function post_content_upload(auth::Authorization,
                             fun::String,
                             args::Union{Nothing, Dict},
                             content
                             )::Dict   # Union{Nothing, Dict}
    post_http(auth, "https://content.dropboxapi.com/2/$fun", args,
              content=content)[1]
end

"""
    post_content_download(auth::Authorization,
                          fun::String,
                          args::Union{Nothing, Dict} = nothing
                         )::Tuple{Dict, Vector{UInt8}}

Post a Content Download request to the Dropbox API.
"""
function post_content_download(auth::Authorization,
                               fun::String,
                               args::Union{Nothing, Dict}
                               )::Tuple{Dict, Vector{UInt8}}
    post_http(auth, "https://content.dropboxapi.com/2/$fun", args,
              expecting_content=true)
end

################################################################################

export calc_content_hash
"""
    calc_content_hash(data::Vector{UInt8})::String

Calculate the content hash of a byte array with Dropbox's algorithm.
"""
function calc_content_hash(data::AbstractVector{UInt8})::String
    chunksums = UInt8[]
    chunksize = 4 * 1024 * 1024
    len = length(data)
    for offset in 1:chunksize:len
        chunk = @view data[offset : min(len, offset+chunksize-1)]
        append!(chunksums, sha256(chunk))
    end
    bytes2hex(sha256(chunksums))
end

"""
    calc_content_hash(
        data_channel::Union{AbstractChannel{<: AbstractVector{UInt8}},
                            AbstractVector{<: AbstractVector{UInt8}}}
    )::String

Calculate a content hash with Dropbox's algorithm from chunks of data.
The data channel is used to pass in the data in chunks. The result is
returned once the data channel is closed.
"""
function calc_content_hash(
    data_channel::Union{AbstractChannel{<: AbstractVector{UInt8}},
                        AbstractVector{<: AbstractVector{UInt8}}})::
    String

    chunksize = 4 * 1024 * 1024
    chunksums = UInt8[]
    # TODO: Don't buffer the data; instead, use SHA256_CTX to handle
    # partial chunks
    buffer = UInt8[]
    for data in data_channel
        append!(buffer, data)
        len = length(buffer)
        if len >= chunksize
            for offset in 1 : chunksize : len-chunksize+1
                chunk = @view buffer[offset : offset+chunksize-1]
                append!(chunksums, sha256(chunk))
            end
            newlen = mod(len, chunksize)
            buffer = buffer[end-newlen+1 : end]
        end
    end
    if !isempty(buffer)
        append!(chunksums, sha256(buffer))
    end
    return bytes2hex(sha256(chunksums))
end

export files_create_folder
"""
    files_create_folder(auth::Authorization,
                        path::String
                       )::FolderMetadata

Create a folder `path`. Returns metadata for the created folder.
"""
function files_create_folder(auth::Authorization,
                             path::String)::FolderMetadata
    args = Dict(
        "path" => path,
        "autorename" => false,
    )
    res = post_rpc(auth, "files/create_folder", args)
    return FolderMetadata(res)
end

export files_delete
"""
    files_delete(auth::Authorization,
                 path::String
                )::Metadata

Delete a file or folder `path` recursively. Returns metadata of the
deleted object.
"""
function files_delete(auth::Authorization,
                      path::String)::Metadata
    args = Dict(
        "path" => path,
        # parent_rev
    )
    res = post_rpc(auth, "files/delete", args)
    return Metadata(res)
end

export files_download
"""
    files_download(auth::Authorization,
                   path::String
                  )::Tuple{FileMetadata, Vector{UInt8}}

Download file `path`. Returns both its metadata and content.
"""
function files_download(auth::Authorization,
                        path::String)::Tuple{FileMetadata, Vector{UInt8}}

    args = Dict(
        "path" => path,
    )
    res, content = post_content_download(auth, "files/download", args)
    metadata = FileMetadata(res)

    # Check content hash
    content_hash = calc_content_hash(content)
    if metadata.content_hash != content_hash
        throw(DropboxError(
            Dict("error" => "LocalContentHashMismatch",
                 "error_summary" => "local/content_hashes_do_not_match",
                 "path" => path,
                 "content_hash" => metadata.content_hash,
                 "local_content_hash" => content_hash,
                 )))
    end

    return metadata, content
end

export files_get_metadata
"""
    files_get_metadata(auth::Authorization,
                       path::String
                      )::Metadata

Get metadata for file or folder `path`.
"""
function files_get_metadata(auth::Authorization,
                            path::String)::Metadata
    args = Dict(
        "path" => path,
        "include_media_info" => false,
        "include_deleted" => false,
        "include_has_explicit_shared_members" => false,
        # "include_property_groups"
    )
    res = post_rpc(auth, "files/get_metadata", args)
    return Metadata(res)
end

export files_list_folder
"""
    files_list_folder(auth::Authorization,
                      path::String;
                      recursive::Bool = false
                     )::Vector{Metadata}

List the contents of folder `path`.
"""
function files_list_folder(auth::Authorization,
                           path::String;
                           recursive::Bool = false)::Vector{Metadata}
    args = Dict(
        "path" => path,
        "recursive" => recursive,
        "include_media_info" => false,
        "include_deleted" => false,
        "include_has_explicit_shared_members" => false,
        "include_mounted_folders" => true,
    )
    res = post_rpc(auth, "files/list_folder", args)
    metadatas = Metadata[Metadata(x) for x in res["entries"]]
    cursor = res["cursor"]
    has_more = res["has_more"]

    while has_more
        args = Dict(
            "cursor" => cursor,
        )
        res = post_rpc(auth, "files/list_folder/continue", args)
        append!(metadatas, Metadata[Metadata(x) for x in res["entries"]])
        cursor = res["cursor"]
        has_more = res["has_more"]
    end

    return metadatas
end

export WriteMode
@enum WriteMode add overwrite # update

export files_upload
"""
    files_upload(auth::Authorization,
                 path::String,
                 content::Vector{UInt8}
                )::FileMetadata

Upload the byte array `content` to a file `path`, returning its
metadata. This function should only be used for small files (< 150
MByte), and if only a few files are uploaded. Other `files_upload`
functions are more efficient for large files and/or if there are many
files to be uploaded.
"""
function files_upload(auth::Authorization,
                      path::String,
                      content::Vector{UInt8})::FileMetadata
    args = Dict(
        "path" => path,
        "mode" => add,
        "autorename" => false,
        # "client_modified"
        "mute" => false,
        # "property_groups"
        "strict_conflict" => false,
    )
    res = post_content_upload(auth, "files/upload", args, content)
    metadata = FileMetadata(res)

    # Check content hash
    content_hash = calc_content_hash(content)
    if metadata.content_hash != content_hash
        throw(DropboxError(
            Dict("error" => "LocalContentHashMismatch",
                 "error_summary" => "local/content_hashes_do_not_match",
                 "path" => path,
                 "content_hash" => metadata.content_hash,
                 "local_content_hash" => content_hash,
                 )))
    end

    return metadata
end

export files_upload
"""
    files_upload(auth::Authorization,
                 path::String,
                 content_channel::Channel{Vector{UInt8}}
                )::FileMetadata

Upload a file `path`. The upload channel is used to pass in the data
in chunks, where each chunk should be no larger than 150 MByte. This
channel needs to be closed after passing in all chunks to finalize the
upload. The return value is then the metadata of the uploaded file.

This function should only be used if only a few files are to be
uploaded. Other `files_upload` functions are more efficient if there
are many files to be uploaded.
"""
function files_upload(auth::Authorization,
                      path::String,
                      content_channel::Union{AbstractChannel{Vector{UInt8}},
                                             AbstractVector{Vector{UInt8}}}
                      )::FileMetadata
    session_id = nothing
    offset = Int64(0)
    data_channel = Channel{Vector{UInt8}}(0)
    content_hash_task = start_task(() -> calc_content_hash(data_channel),
                                   (:files_upload, :calc_content_hash, path))
    for chunk in content_channel
        isempty(chunk) && continue
        if session_id === nothing
            args = Dict(
                "close" => false,
            )
            res = post_content_upload(auth, "files/upload_session/start",
                                      args, chunk)
            session_id = res["session_id"]
        else
            args = Dict(
                "cursor" => Dict(
                    "session_id" => session_id,
                    "offset" => offset,
                ),
                "close" => false,
            )
            res = post_content_upload(auth, "files/upload_session/append_v2",
                                      args, chunk)
        end
        offset = offset + length(chunk)
        put!(data_channel, chunk)
    end
    if session_id === nothing
        # The file was empty
        @assert offset == 0
        metadata = files_upload(auth, path, UInt8[])
    else
        # Note: We don't need to close the session
        args = Dict(
            "cursor" => Dict(
                "session_id" => session_id,
                "offset" => offset,
            ),
            "commit" => Dict(
                "path" => path,
                "mode" => add,
                "autorename" => false,
                # "client_modified"
                "mute" => false,
                # "property_groups"
                "strict_conflict" => false,
            ),
        )
        res = post_content_upload(auth, "files/upload_session/finish",
                                  args, UInt8[])
        metadata = FileMetadata(res)
    end

    # Check content hash
    close(data_channel)
    content_hash = fetch(content_hash_task)
    if metadata.content_hash != content_hash
        throw(DropboxError(
            Dict("error" => "LocalContentHashMismatch",
                 "error_summary" => "local/content_hashes_do_not_match",
                 "path" => path,
                 "content_hash" => metadata.content_hash,
                 "local_content_hash" => content_hash,
                 )))
    end

    return metadata
end

export UploadSpec
"""
    struct UploadSpec
        destination::String
        content_channel::Union{AbstractChannel{Vector{UInt8}},
                               AbstractVector{Vector{UInt8}}}
    end

Specify both path and content for a file that is to be uploaded. The
content needs to be passed in as chunks via a channel.
"""
struct UploadSpec
    destination::String
    content_channel::Union{AbstractChannel{Vector{UInt8}},
                           AbstractVector{Vector{UInt8}}}
end

mutable struct UploadState
    path::String
    session_id::String
    offset::Int64
    content_hash::String
    metadata::Union{Nothing, Metadata}
end

"""
    files_upload(auth::Authorization,
                 upload_spec_channel::Union{AbstractChannel{UploadSpec},
                                            AbstractVector{UploadSpec}}
                )::Vector{Metadata}

Uploading several files simultaneously in an efficient manner. The
upload spec channel is used to describe the uploaded files. This
channel needs to be closed after passing in all upload specs to
finalize the uploads. The return value is then a vector of all
metadata of the uploaded files.

This function is efficient if many files are uploaded.
"""
function files_upload(auth::Authorization,
                      upload_spec_channel::Union{AbstractChannel{UploadSpec},
                                                 AbstractVector{UploadSpec}}
                      )::Vector{Metadata}
    # TODO: can handle only 1000 files at once
    # TODO: parallelize loop
    upload_tasks = Task[]
    for upload_spec in upload_spec_channel
        push!(upload_tasks,
              start_task(() -> upload_one_file(auth, upload_spec),
                         (:files_upload, :upload_one_file,
                          upload_spec.destination)))
    end
    upload_states = UploadState[]
    for t in upload_tasks
        push!(upload_states, fetch(t))
    end

    if isempty(upload_states)
        # We uploaded zero files
        return Metadata[]
    end

    # We might need to retry several times
    while true

        missing_uploads = Int[]
        for (i, upload_state) in enumerate(upload_states)
            if upload_state.metadata isa Nothing
                push!(missing_uploads, i)
            end
        end
        @assert !isempty(missing_uploads)

        entries = []
        for i in missing_uploads
            upload_state = upload_states[i]
            @assert upload_state.metadata isa Nothing
            args = Dict(
                "cursor" => Dict(
                    "session_id" => upload_state.session_id,
                    "offset" => upload_state.offset,
                ),
                "commit" => Dict(
                    "path" => upload_state.path,
                    "mode" => add,
                    "autorename" => false,
                    # "client_modified"
                    "mute" => false,
                    # "property_groups"
                    "strict_conflict" => false,
                ),
            )
            push!(entries, args)
        end
        args = Dict(
            "entries" => entries,
        )
        res = post_rpc(auth, "files/upload_session/finish_batch", args)
        is_complete = res[".tag"] == "complete"

        # Wait for completion
        if !is_complete
            async_job_id = res["async_job_id"]

            delay = 1.0         # seconds
            while !is_complete
                sleep(delay)
                delay = min(60.0, 2.0 * delay) # exponential back-off

                args = Dict(
                    "async_job_id" => async_job_id,
                )
                res = post_rpc(auth, "files/upload_session/finish_batch/check",
                               args)
                is_complete = res[".tag"] == "complete"
            end
        end
        entries = res["entries"]

        @assert length(entries) == length(missing_uploads)
        for (i, entry) in zip(missing_uploads, entries)
            if entry[".tag"] == "success"
                # The tag is "success", but the type is actually
                # "FileMetadata". This is strange, and also doesn't
                # agree with the documentation.
                success = entry
                upload_state = upload_states[i]
                @assert upload_state.metadata isa Nothing

                metadata = FileMetadata(success)
                if metadata.content_hash != upload_state.content_hash
                    throw(DropboxError(
                        Dict("error" => "LocalContentHashMismatch",
                             "error_summary" =>
                             "local/content_hashes_do_not_match",
                             "path" => upload_state.path,
                             "content_hash" => metadata.content_hash,
                             "local_content_hash" =>
                             upload_state.content_hash,
                             )))
                end
                upload_state.metadata = metadata
            else
                @assert entry[".tag"] == "failure"
                failure = entry["failure"]
                if failure[".tag"] == "too_many_write_operations"
                    # we will retry this entry
                elseif (failure[".tag"] == "path" &&
                        failure["path"][".tag"] == "disallowed_name")
                    # we will ignore this entry
                    upload_state = upload_states[i]
                    @assert upload_state.metadata isa Nothing
                    metadata = ErrorMetadata(entry)
                    upload_state.metadata = metadata
                else
                    throw(DropboxError(entry))
                end
            end
        end

        if any(upload_state.metadata isa Nothing
               for upload_state in upload_states)
            println("Info: Error \"too_many_write_operations\"")
            println("Info: Waiting for 1 seconds...")
            sleep(1)
            println("Info: Retrying...")
            continue
        end

        return Metadata[upload_state.metadata for upload_state in upload_states]
    end
end

function upload_one_file(auth::Authorization,
                         upload_spec::UploadSpec)::UploadState
    session_id = nothing
    offset = Int64(0)
    data_channel = Channel{Vector{UInt8}}(0)
    content_hash_task = start_task(() -> calc_content_hash(data_channel),
                                   (:upload_one_file, :calc_content_hash,
                                    upload_spec.destination))
    for chunk in upload_spec.content_channel
        isempty(chunk) && continue
        if session_id === nothing
            args = Dict(
                "close" => false,
            )
            res = post_content_upload(auth, "files/upload_session/start",
                                      args, chunk)
            session_id = res["session_id"]
        else
            args = Dict(
                "cursor" => Dict(
                    "session_id" => session_id,
                    "offset" => offset,
                ),
                "close" => false,
            )
            res = post_content_upload(auth, "files/upload_session/append_v2",
                                      args, chunk)
        end
        offset += length(chunk)
        put!(data_channel, chunk)
    end
    # TODO: We need to close only the last session. But what does
    # "last" mean? Is it "last session id passed to
    # files/finish_upload_batch", or the last session to finish
    # uploading?
    if session_id === nothing
        # The file is empty
        @assert offset == 0
        args = Dict(
            "close" => true,
        )
        res = post_content_upload(auth, "files/upload_session/start",
                                  args, UInt8[])
        session_id = res["session_id"]
    else
        args = Dict(
            "cursor" => Dict(
                "session_id" => session_id,
                "offset" => offset,
            ),
            "close" => true,
        )
        res = post_content_upload(auth, "files/upload_session/append_v2",
                                  args, UInt8[])
    end

    close(data_channel)
    content_hash = fetch(content_hash_task)

    return UploadState(upload_spec.destination, session_id,
                       offset, content_hash, nothing)
end

export Name
struct Name
    given_name::String
    surname::String
    familiar_name::String
    display_name::String
    abbreviated_name::String
end
Name(d::Dict) = Name(
    d["given_name"],
    d["surname"],
    d["familiar_name"],
    d["display_name"],
    d["abbreviated_name"],)

export AccountType
@enum AccountType basic pro business
AccountType(d::Dict) = Dict(
    "basic" => basic,
    "pro" => pro,
    "business" => business,
)[d[".tag"]]

export RootInfo
abstract type RootInfo end
RootInfo(d::Dict) = Dict(
    "team" => TeamRootInfo,
    "user" => UserRootInfo,
)[d[".tag"]](d)

export TeamRootInfo
struct TeamRootInfo <: RootInfo
    root_namespace_id::String
    home_namespace_id::String
    home_path::String
end
TeamRootInfo(d::Dict) = TeamRootInfo(
    d["root_namespace_id"],
    d["home_namespace_id"],
    d["home_path"],
)

export UserRootInfo
struct UserRootInfo <: RootInfo
    root_namespace_id::String
    home_namespace_id::String
end
UserRootInfo(d::Dict) = UserRootInfo(
    d["root_namespace_id"],
    d["home_namespace_id"],
)

export SharedFolderMemberPolicy
@enum SharedFolderMemberPolicy team anyone
SharedFolderMemberPolicy(d::Dict) = Dict(
    "team" => team,
    "anyone" => anyone,
)[d[".tag"]]

export SharedFolderJoinPolicy
@enum SharedFolderJoinPolicy from_team_only from_anyone
SharedFolderJoinPolicy(d::Dict) = Dict(
    "from_team_only" => from_team_only,
    "from_anyone" => from_anyone,
)[d[".tag"]]

export SharedLinkCreatePolicy
@enum SharedLinkCreatePolicy default_public default_team_only team_only
SharedLinkCreatePolicy(d::Dict) = Dict(
    "default_public" => default_public,
    "default_team_only" => default_team_only,
    "team_only" => team_only,
)[d[".tag"]]

export TeamSharingPolicies
struct TeamSharingPolicies
    shared_folder_member_policy::SharedFolderMemberPolicy
    shared_folder_join_policy::SharedFolderJoinPolicy
    shared_link_create_policy::SharedLinkCreatePolicy
end
TeamSharingPolicies(d::Dict) = TeamSharingPolicies(
    SharedFolderMemberPolicy(d["shared_folder_member_policy"]),
    SharedFolderJoinPolicy(d["shared_folder_join_policy"]),
    SharedLinkCreatePolicy(d["shared_link_create_policy"]),
)

export OfficeAddInPolicy
@enum OfficeAddInPolicy disabled enabled
OfficeAddInPolicy(d::Dict) = Dict(
    "disabled" => disabled,
    "enabled" => enabled,
)[d[".tag"]]

export FullTeam
struct FullTeam
    id::String
    name::String
    sharing_policies::TeamSharingPolicies
    office_addin_policy::OfficeAddInPolicy
end
FullTeam(d::Dict) = FullTeam(
    d["id"],
    d["name"],
    TeamSharingPolicies(d["sharing_policies"]),
    OfficeAddInPolicy(d["office_addin_policy"]),
)

export FullAccount
struct FullAccount
    account_id::String
    name::Name
    email::String
    email_verified::Bool
    disabled::Bool
    locale::String
    referral_link::String
    is_paired::Bool
    account_type::AccountType
    root_info::RootInfo
    profile_photo_url::Union{Nothing, String}
    country::Union{Nothing, String}
    team::Union{Nothing, FullTeam}
    team_member_id::Union{Nothing, String}
end
FullAccount(d::Dict) = FullAccount(
    d["account_id"],
    Name(d["name"]),
    d["email"],
    d["email_verified"],
    d["disabled"],
    d["locale"],
    d["referral_link"],
    d["is_paired"],
    AccountType(d["account_type"]),
    RootInfo(d["root_info"]),
    get(d, "profile_photo_url", nothing),
    get(d, "country", nothing),
    mapget(x->FullTeam(x), d, "team"),
    get(d, "team_member_id", nothing),
)

export users_get_current_account
"""
    users_get_current_account(auth::Authorization)::FullAccount

Get information about the current account, i.e. the account associated
with the account token in the authorization `auth`.
"""
function users_get_current_account(auth::Authorization)::FullAccount
    res = post_rpc(auth, "users/get_current_account")
    return FullAccount(res)
end

export SpaceAllocation
abstract type SpaceAllocation end
SpaceAllocation(d::Dict) = Dict(
    "individual" => IndividualSpaceAllocation,
    "team" => TeamSpaceAllocation,
)[d[".tag"]](d)

export IndividualSpaceAllocation
struct IndividualSpaceAllocation <: SpaceAllocation
    allocated::Int64
end
IndividualSpaceAllocation(d::Dict) = IndividualSpaceAllocation(
    d["allocated"],
)

export MemberSpaceLimitType
@enum MemberSpaceLimitType off alert_only stop_sync
MemberSpaceLimitType(d::Dict) = Dict(
    "off" => off,
    "alert_only" => alert_only,
    "stop_sync" => stop_sync,
)[d[".tag"]]

export TeamSpaceAllocation
struct TeamSpaceAllocation <: SpaceAllocation
    used::Int64
    allocated::Int64
    user_within_team_space_allocated::Int64
    user_within_team_space_limit_type::MemberSpaceLimitType
end
TeamSpaceAllocation(d::Dict) = TeamSpaceAllocation(
    d["used"],
    d["allocated"],
    d["user_within_team_space_allocated"],
    MemberSpaceLimitType(d["user_within_team_space_limit_type"]),
)

export SpaceUsage
struct SpaceUsage
    used::Int64
    allocation::SpaceAllocation
end
SpaceUsage(d::Dict) = SpaceUsage(
    d["used"],
    SpaceAllocation(d["allocation"]),
)

export users_get_space_usage
"""
    users_get_space_usage(auth::Authorization)::SpaceUsage

Get the space usage for the current account.
"""
function users_get_space_usage(auth::Authorization)::SpaceUsage
    res = post_rpc(auth, "users/get_space_usage")
    return SpaceUsage(res)
end

include("DropboxCLI.jl")

end


text:
"""
    SeisPlot(d [, extent]; <keyword arguments>)

Plot time-space, frequency-wavenumber or amplitude-frequency 2D seismic data `d`
with color, wiggles or overlay.

# Arguments
* `d`: 2D data to plot.
* `extent`: extent of the data (optional).

# Keyword arguments
* `plot_type="TX"`: `"TX"` for time-space plot; `"FK"` for frequency-wavenumber
plot or `"Amplitude"` for amplitude spectrum plot.
* `style="color"`: style of the plot: `"color"`, `"wiggles"` or `"overlay"`.
* `cmap="PuOr"`: colormap for  `"color"` or `"overlay"` style.
* `pclip=98`: percentile for determining clip.
* `vmin="NULL"`: minimum value used in colormapping data.
* `vmax="NULL"`: maximum value used in colormapping data.
* `aspect="auto"`: color image aspect ratio.
* `interpolation="Hanning"`: interpolation method for colormapping data.
* `fmax=100`: maximum frequency for `"FK"` or `"Amplitude"` plot.
* `wiggle_fill_color="k"`: color for filling the positive wiggles.
* `wiggle_line_color="k"`: color for wiggles' lines.
* `wiggle_trace_increment=1`: increment for wiggle traces.
* `xcur=1.2`: wiggle excursion in traces corresponding to clip.
* `scal="NULL"`: scale for wiggles.
* `title=" "`: title of plot.
* `titlesize=16`: size of title.
* `xlabel=" "`: label on x-axis.
* `xunits=" "`: units of y-axis.
* `ylabel=" "`: label on x-axis.
* `yunits=" "`: units of y-axis.
* `labelsize=14`: size of labels on axis.
* `ox=0`: first point of x-axis.
* `dx=1`: increment of x-axis.
* `oy=0`: first point of y-axis.
* `dy=1`: increment of y-axis.
* `xticks="NULL"`: ticks on x-axis.
* `yticks="NULL"`: ticks on y-axis.
* `xticklabels="NULL"`: labels on ticks of x-axis.
* `yticklabels="NULL"`: labels on ticks of y-axis.
* `ticksize=11`: size of labels on ticks.
* `fignum="NULL"`: number of figure.
* `wbox=6`: width of figure in inches.
* `hbox=6`: height of figure in inches.
* `dpi=100`: dots-per-inch of figure.
* `name="NULL"`: name of the figure to save (only if `name` is given).

# Example
```julia
julia> d, extent = SeisLinearEvents(); SeisPlot(d);
```

Credits: Aaron Stanton, 2015
"""
function SeisPlot{T<:Real}(d::Array{T,2}; plot_type="TX", style="color",
                           cmap="PuOr", pclip=98, vmin="NULL", vmax="NULL",
                           aspect="auto", interpolation="Hanning", fmax=100,
                           wiggle_fill_color="k", wiggle_line_color="k",
                           wiggle_trace_increment=1, xcur=1.2, scal="NULL",
                           title=" ", titlesize=16, xlabel=" ", xunits=" ",
                           ylabel=" ", yunits=" ", labelsize=14, ox=0, dx=1,
                           oy=0, dy=1, xticks="NULL", yticks="NULL",
                           xticklabels="NULL", yticklabels="NULL", ticksize=11,
                           fignum="NULL", wbox=6, hbox=6, dpi=100, name="NULL")
    if (vmin=="NULL" || vmax=="NULL")
        if (pclip<=100)
	    a = -quantile(abs.(d[:]), (pclip/100))
	else
	    a = -quantile(abs.(d[:]), 1)*pclip/100
	end
	b = -a
    else
	a = vmin
	b = vmax
    end
    plt.ion()
    if (fignum == "NULL")
	fig = plt.figure(figsize=(wbox, hbox), dpi=dpi, facecolor="w",
                           edgecolor="k")
    else
	fig = plt.figure(num=fignum, figsize=(wbox, hbox), dpi=dpi,
                           facecolor="w", edgecolor="k")
    end
    if plot_type == "TX"
	if (style != "wiggles")
	    im = plt.imshow(d, cmap=cmap, vmin=a, vmax=b,
                            extent=[ox - dx/2,ox + (size(d,2)-1)*dx + dx/2,
                                    oy + (size(d,1)-1)*dy,oy],
                            aspect=aspect, interpolation=interpolation)
	end
	if (style != "color")
            style=="wiggles" ? margin = dx : margin = dx/2
	    y = oy+dy*collect(0:1:size(d, 1)-1)
	    x = ox+dx*collect(0:1:size(d, 2)-1)
	    delta = wiggle_trace_increment*dx
	    hmin = minimum(x)
	    hmax = maximum(x)
            dmax = maximum(abs.(d[:]))
	    alpha = xcur*delta
            scal=="NULL" ? alpha = alpha/maximum(abs.(d[:])) : alpha=alpha*scal
	    for k = 1:wiggle_trace_increment:size(d, 2)
		x_vert = Float64[]
		y_vert = Float64[]
		sc = x[k] * ones(size(d, 1))
    s  = d[:,k]*alpha + sc
		im = plt.plot( s, y, wiggle_line_color)
		if (style != "overlay")
		    plt.fill_betweenx(y, sc, s, where=s.>sc, facecolor=wiggle_line_color)
		end
	    end
	    plt.axis([ox - margin, ox + (size(d, 2)-1)*dx + margin,
                      oy + (size(d, 1)-1)*dy, oy])
	end
    elseif plot_type == "FK"
	xlabel = "Wavenumber"
	xunits = "(1/m)"
	ylabel = "Frequency"
	yunits = "Hz"
	dk = 1/dx/size(d[:,:], 2)
	kmin = -dk*size(d[:,:], 2)/2
	kmax =  dk*size(d[:,:], 2)/2
	df = 1/dy/size(d[:,:], 1)
	FMAX = df*size(d[:,:], 1)/2
	if fmax > FMAX
	    fmax = FMAX
	end
	nf = convert(Int32, floor((size(d[:, :], 1)/2)*fmax/FMAX))
	D = abs.(fftshift(fft(d[:, :])))
	D = D[round(Int,end/2):round(Int,end/2)+nf, :]
	if (vmin=="NULL" || vmax=="NULL")
	    a = 0.
	    if (pclip<=100)
		b = quantile(abs.(D[:]), (pclip/100))
	    else
		b = quantile(abs.(D[:]), 1)*pclip/100
	    end
	end
	im = plt.imshow(D, cmap=cmap, vmin=a, vmax=b, extent=[kmin,kmax,fmax,0],
                        aspect=aspect, interpolation=interpolation)
    elseif plot_type == "Amplitude"
	xlabel = "Frequency"
	xunits = "(Hz)"
	ylabel = "Amplitude"
	yunits = ""
	nx = size(d[:,:], 2)
	df = 1/dy/size(d[:, :], 1)
	FMAX = df*size(d[:, :], 1)/2
	if fmax > FMAX
	    fmax = FMAX
	end
	nf = convert(Int32, floor((size(d[:, :], 1)/2)*fmax/FMAX))
	y = fftshift(sum(abs.(fft(d[:, :], 1)), 2))/nx
	y = y[round(Int,end/2):round(Int, end/2)+nf]
	norm = maximum(y[:])
	if (norm > 0.)
		y = y/norm
	end
	x = collect(0:df:fmax)
	im = plt.plot(x, y)
	plt.title(title)
	plt.xlabel(join([xlabel " " xunits]))
	plt.ylabel(join([ylabel " " yunits]))
	plt.axis([0, fmax, 0, 1.1])
    else
	error("plot_type not recognized.")
    end
    plt.title(title, fontsize=titlesize)
    plt.xlabel(join([xlabel " " xunits]), fontsize=labelsize)
    plt.ylabel(join([ylabel " " yunits]), fontsize=labelsize)
    xticks == "NULL" ? nothing : plt.xticks(xticks)
    yticks == "NULL" ? nothing : plt.yticks(yticks)
    ax = plt.gca()
    xticklabels == "NULL" ? nothing : ax[:set_xticklabels](xticklabels)
    yticklabels == "NULL" ? nothing : ax[:set_yticklabels](yticklabels)
    plt.setp(ax[:get_xticklabels](), fontsize=ticksize)
    plt.setp(ax[:get_yticklabels](), fontsize=ticksize)
    if (name == "NULL")
	plt.show()
    else
	plt.savefig(name, dpi=dpi)
	plt.close()
    end
    return im
end

function SeisPlot{T<:Real}(d::Array{T,2}, extent::Extent;
                           plot_type="TX", style="color",
                           cmap="PuOr", pclip=98, vmin="NULL", vmax="NULL",
                           aspect="auto", interpolation="Hanning", fmax=100,
                           wiggle_fill_color="k", wiggle_line_color="k",
                           wiggle_trace_increment=1, xcur=1.2, scal="NULL",
                           title=" ", titlesize=16, xlabel=" ", xunits=" ",
                           ylabel=" ", yunits=" ", labelsize=14, ox=0, dx=1,
                           oy=0, dy=1, xticks="NULL", yticks="NULL",
                           xticklabels="NULL", yticklabels="NULL", ticksize=11,
                           fignum="NULL", wbox=6, hbox=6, dpi=100, name="NULL")
    im = SeisPlot(d, plot_type=plot_type, style=style, cmap=cmap, pclip=pclip,
                  vmin=vmin, vmax=vmax, aspect=aspect,
                  interpolation=interpolation, fmax=fmax,
                  wiggle_fill_color=wiggle_fill_color,
                  wiggle_line_color=wiggle_line_color,
                  wiggle_trace_increment=wiggle_trace_increment, xcur=xcur,
                  scal=scal, title=extent.title,  titlesize=titlesize,
                  xlabel=extent.label2, xunits=join(["(",extent.unit2,")"]),
                  ylabel=extent.label1, yunits=join(["(",extent.unit1,")"]),
                  labelsize=labelsize, ox=extent.o2, dx=extent.d2, oy=extent.o1,
                  dy=extent.d1, xticks=xticks, yticks=yticks,
                  xticklabels=xticklabels, yticklabels=yticklabels,
                  ticksize=ticksize, wbox=wbox, hbox=hbox, dpi=dpi, name=name,
                  fignum=fignum)
    return im
end

ext: jl

max_stars_repo_name: fercarozzi/myseismicjulia

max_issues_repo_name: fercarozzi/myseismicjulia

max_forks_repo_name: fercarozzi/myseismicjulia


text:
normalizecolumns(m::AbstractArray{T, 2}) where {T<:Number} = m ./ sqrt.(sum(m .^ 2, dims = 1) .+ eps(T))
function normalizecolumns!(m::AbstractArray{T, 2}) where {T<:Number}
	m .= normalizecolumns(m)
end

"""
		hsplitsoftp(x, ϵ = 1f-5)

		Splits x horizontally into two equal parts and use softplus to the lower part.
		ϵ is added to the soft-plus to prevent underflow and improve the numerical stability

"""
hsplitsoftp(x,ϵ = 1f-5) = x[1:size(x, 1) ÷ 2, :], softplus.(x[size(x, 1) ÷ 2 + 1 : 2 * (size(x, 1) ÷ 2), :] .+ ϵ)
hsplit1softp(x,ϵ = 1f-5) = x[1:size(x, 1) - 1, :], softplus.(x[end, :] .+ ϵ)

"""
		kldiv(μ,σ2)

		kl-divergence of a Gaussian min mean `μ` and diagonal variance `σ^2`
		to N(0,I)
"""
kldiv(μ,σ2) = - mean(sum((@.log(σ2) - μ^2 - σ2), dims = 1))

log_normal(x) = - sum((x.^2), dims = 1) ./ 2 .- size(x, 1) * log(2f0 * π) / 2
log_normal(x, μ) = log_normal(x - μ)
log_normal(x, μ, σ2::Vector{T}) where {T<:Number} = - sum((x - μ) .^ 2 ./ σ2' .+ log.(σ2' .* 2π), dims = 1) / 2
log_normal(x, μ, Σ::Matrix{T}) where {T<:Number} = - (log(det(Σ)) + (x - μ)' * inv(Σ) * (x - μ) + size(Σ, 1) * log(2π)) / 2

"""
	vmfentropy(m, κ)

	Entropy of Von Mises-Fisher distribution
"""
vmfentropy(m, κ) = .-κ .* besselix(m / 2, κ) ./ besselix(m / 2 - 1, κ) .- ((m ./ 2 .- 1) .* log.(κ) .- (m ./ 2) .* log(2π) .- (κ .+ log.(besselix(m / 2 - 1, κ))))

"""
	huentropy(m)

	Entropy of Hyperspherical Uniform distribution
"""
huentropy(m) = m / 2 * log(π) + log(2) - lgamma(m / 2)

# Likelihood estimation of a sample x under VMF with given parameters taken from https://pdfs.semanticscholar.org/2b5b/724fb175f592c1ff919cc61499adb26996b1.pdf
# normalizing constant for density function of VMF
c(p, κ) = κ ^ (p / 2 - 1) / ((2π) ^ (p / 2) * besseli(p / 2 - 1, κ))

# log likelihood of one sample under the VMF dist with given parameters
log_vmf_c(x, μ, κ) = κ * μ' * x .+ log(c(length(μ), κ))
log_vmf_c(x::AbstractMatrix, μ::AbstractMatrix, κ::T) where {T <: Number} = [log_vmf_c(x[:, i], μ[:, i], κ) for i in size(x, 2)]
log_vmf_c(x::AbstractMatrix, μ::AbstractMatrix, κ::AbstractVector) = [log_vmf_c(x[:, i], μ[:, i], κ[i]) for i in size(x, 2)]
log_vmf_wo_c(x, μ, κ) = κ * μ' * x

pairwisecos(x, y) = max.(1 .- (x' * y), 0) # This is a bit of a hack to avoid the distance being negative due to float
pairwisecos(x) = pairwisecos(x, x)

k_imq(x, y, c) = sum( c./ (c .+ pairwisecos(x, y))) / (size(x, 2) * size(y, 2))
k_imq(x::T, c) where {T <: AbstractMatrix} = sum(c ./ (c .+ pairwisecos(x))) / (size(x, 2) * (size(x, 2) - 1))
k_imq(x::T, c) where {T <: AbstractVector} = zero(eltype(x))

mmd_imq(x,y,c) = k_imq(x,c) + k_imq(y,c) - 2 * k_imq(x,y,c)

function gaussiansample(μ, σ2)
	ϵ = randn!(similar(μ,size(μ)))
	μ .+ sqrt.(σ2) .* ϵ
end

function samplehsuniform(size...)
	v = randn(size...)
	v = normalizecolumns(v)
end

max_stars_repo_name: honzabim/FewShotAnomalyDetection

max_issues_repo_name: honzabim/FewShotAnomalyDetection

max_forks_repo_name: honzabim/FewShotAnomalyDetection


text:
@testset "exponentiated" begin
    rng = MersenneTwister(123456)
    x = rand(rng)*2
    v1 = rand(rng, 3)
    v2 = rand(rng, 3)

    k = ExponentiatedKernel()
    @test kappa(k,x) ≈ exp(x)
    @test kappa(k,-x) ≈ exp(-x)
    @test k(v1,v2) ≈ exp(dot(v1,v2))
    @test metric(ExponentiatedKernel()) == KernelFunctions.DotProduct()
    @test repr(k) == "Exponentiated Kernel"
    test_ADs(ExponentiatedKernel)
end


text:
# This file was generated, do not modify it. # hide
train, test = MLJ.partition(collect(eachindex(targets)), 0.70, rng=52)

max_issues_repo_name: alan-turing-institute/MLJTutorials

max_forks_repo_name: alan-turing-institute/MLJTutorials


text:
# Note that this script can accept some limited command-line arguments, run
# `julia build_tarballs.jl --help` to see a usage message.
using BinaryBuilder, Pkg

name = "LinearFold"
version = v"1.0.0"

# Collection of sources required to complete build
sources = [
    ArchiveSource("https://github.com/LinearFold/LinearFold/archive/refs/tags/v$(version.major).$(version.minor).tar.gz",
                  "2ae56b5f183472c2de96782e770a91e57f82e0ab511dfc0d9d612aa4e6155f60"),
]

# Bash recipe for building across all platforms
script = raw"""
cd $WORKSPACE/srcdir/LinearFold-*/

make -j${nproc} CC=${CXX}
for prg in linearfold_c linearfold_v; do
    install -Dvm 755 "bin/${prg}" "${bindir}/${prg}${exeext}"
done

install_license LICENSE
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = supported_platforms()
platforms = expand_cxxstring_abis(platforms)

# The products that we will ensure are always built
products = [
    ExecutableProduct("linearfold_c", :linearfold_c),
    ExecutableProduct("linearfold_v", :linearfold_v),
]

# Dependencies that must be installed before this package can be built
dependencies = Dependency[]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies; julia_compat="1.6")


text:
using JSON, Printf
import Base: merge, length

abstract type AbstractExtractor end
abstract type BagExtractor <: AbstractExtractor end
abstract type JSONEntry end
StringOrNumber = Union{AbstractString,Number}

function safe_update!(s::JSONEntry, d; path = "")
	success = update!(s, d)
	isnothing(success) && return nothing
	success && return s
	@info "In path $path: Instability in the schema detected. Using multiple representation."
	s = MultiEntry([s], s.updated)
	update!(s, d, path=path)
	return s
end
safe_update!(::Nothing, d; path = "") = newentry!(d)
update!(s::JSONEntry, d; path = "") = false

include("scalar.jl")
include("dict.jl")
include("array.jl")
include("multientry.jl")
include("makeschema.jl")

# todo: test if schema for ints and floats if different oderings behaves the same
# todo: test if schema where samples are string and abstractstring is working properly
#   maybe treat all strings as abstractstrings?
# todo: test if I have string and dict in same key in json, and that schema building behaves the same not matter that order are they in
#     (it should not matter if it first sees dict or string)

updated(s::T) where {T<:JSONEntry} = s.updated
merge(combine::typeof(merge), es::JSONEntry...) = merge(es...)
merge(::Nothing, e::JSONEntry) = e

make_representative_sample(sch::JSONEntry, ex::AbstractExtractor) = ex(sample_synthetic(sch))

function Mill.reflectinmodel(sch::JSONEntry, ex::AbstractExtractor, fm=d->Flux.Dense(d, 10), fa=d->BagCount(SegmentedMeanMax(d)); fsm = Dict(), fsa = Dict(),
			   single_key_identity=true, single_scalar_identity=true)
	# because we have type-stable extractors, we now have information about what is missing and what not inside types
	# so I don't have to extract empty and missing samples, the logic is now part of suggestextractor
	specimen = make_representative_sample(sch, ex)
	reflectinmodel(specimen, fm, fa; fsm, fsa, single_key_identity, single_scalar_identity)
end

# this can probably be
make_selector(s) = e->select_by_string(s, e)
select_by_string(s::AbstractString, e::DictEntry) = e.childs[Symbol(s)]
select_by_string(s::AbstractString, e::ArrayEntry) = s == "[]" ? e.items : @error "wrong selector"
select_by_string(s::AbstractString, e::MultiEntry) = e.childs[parse(Int, s)]

"""
Deletes `field` at the specified `path` from the schema `sch`.
For instance, the following:
	`delete!(schema, ".field.subfield.[]", "x")`
deletes the field `x` from `schema` at:
	`schema.childs[:field].childs[:subfield].items.childs`
"""
function Base.delete!(sch::JSONEntry, path::AbstractString, field::AbstractString)
	@assert field != "[]"
	selectors = split(path, ".")[2:end]
	item = reduce((s, f) -> f(s), make_selector.(selectors), init=sch)
	delete!(item.childs, Symbol(field))
end

"""
	prune_json(json, schema)

Removes keys from `json` which are not part of the `schema`.

# Example
```jldoctest
julia> using JSON

julia> j1 = JSON.parse("{\\"a\\": 4, \\"b\\": {\\"a\\":1, \\"b\\": 1}}");

julia> j2 = JSON.parse("{\\"a\\": 4, \\"b\\": {\\"a\\":1}}");

julia> sch = JsonGrinder.schema([j1,j2])
[Dict] \t# updated = 2
  ├── a: [Scalar - Int64], 1 unique values \t# updated = 2
  └── b: [Dict] \t# updated = 2
           ├── a: [Scalar - Int64], 1 unique values \t# updated = 2
           └── b: [Scalar - Int64], 1 unique values \t# updated = 1

julia> j3 = Dict("a" => 4, "b" => Dict("a"=>1), "c" => 1, "d" => 2)
Dict{String, Any} with 4 entries:
  "c" => 1
  "b" => Dict("a"=>1)
  "a" => 4
  "d" => 2

julia> JsonGrinder.prune_json(j3, sch)
Dict{String, Any} with 2 entries:
  "b" => Dict("a"=>1)
  "a" => 4
```
so the `JsonGrinder.prune_json` removes keys `c` and `d`.
"""
prune_json(json, sch::Entry) = json

prune_json(json, sch::ArrayEntry) = map(json) do el
	prune_json(el, sch.items)
end

prune_json(json, sch::DictEntry) =
    Dict(String(k) => prune_json(json[String(k)], v) for (k,v) in children(sch) if String(k) ∈ keys(json))


text:
module day5

using ..aoc19: run_program!

readInput() = parse.(Int,split(readline(joinpath(@__DIR__, "input.txt")), ','))

function part1(data, userInput = 1)
    output = Channel{Int}(Inf)
    input = Channel{Int}(Inf)
    put!(input, userInput)
    run_program!(copy(data), input, output)
    val = take!(output)
    while val == 0
        val = take!(output)
    end
    val
end

part2(input) = part1(input, 5)

end


text:
# This file is an example of fitting an orbit to exoplanet astrometry
# using our new package, DirectDetections.

# We will use the paper "An Updated Visual Orbit of the Directly Imaged
# Exoplanet 51 Eridani b and Prospects for a Dynamical Mass Measurement with Gaia"
# by Rosa et al, 2019
# https://iopscience.iop.org/article/10.3847/1538-3881/ab4da4

# You can copy and paste these blocks one at a time, or modify it
# and run it all by typing: include("example-1.jl")

## Setup: requires Julia 1.7
# Run this once to install the necessary packages.
# You only need to run this once, ever.
cd(@__DIR__)
using Pkg
Pkg.activate(".")
Pkg.update()
Pkg.Registry.add(RegistrySpec(url="https://github.com/sefffal/DirectRegistry.git"))
Pkg.instantiate()

## Activate environment and import packages
cd(@__DIR__)
Pkg.activate(".")
using DirectDetections, Distributions

## Create a 1-planet model
@named b = Planet(
    # Here we specify priors for different orbital parameters.
    # We'll start with uniform priors; however, uniform
    # priors are not ideal for real world orbit fitting.
    Priors(
        # Semi-major axis in AU:
        a = Uniform(1, 75),
        # The orbital inclination in radians.
        # The Sine() distribution gives a uniform distribution
        # orbits points in spherical coordinates:
        i = Sine(),
        # Eccentricity:
        e = Uniform(0,  1),
        # Longitude of the ascending node (i.e. orientation of the orbital plane)
        Ω = Uniform(0,  π),
        # Argument of periapsis (where does the planet come closest to the star)
        ω = Uniform(0, 2π),
        # Epoch of periastron passage, as fraction of orbital period after 2020-01-01.
        # (when did the planet make it's closest approach.)
        τ = Uniform(0, 1)
    ),
    # List any astrometry points we have for the planet.
    # Fill this in from Table 2. You will have to convert from
    # PA angle (deg) and separation (mas) to Δra, Δdec.
    # It's okay to estimate the σ_ra and σ_dec uncertainties.
    # Note: all values MUST include a decimal place.
    Astrometry(
        # Examples (replace data with table 2 in paper. 13 rows instead of 2)
        (epoch=12345., ra=123.0, dec=123.0, σ_ra=10., σ_dec=10.),
        (epoch=12645., ra=223.0, dec=-123.0, σ_ra=100., σ_dec=10.),
    )
)

# We place our planet model into a model of the whole system.
# You will be prompted to download a small star catalog the first time you run this.
@named cEri = System(
    Priors(
        # plx represents the parallax distance in milliarcseconds to the system.
        # Replace this ID with the GAIA EDR3 identifier for 51 Eri (aka cEri).
        # You can find it on SIMBAD: http://simbad.u-strasbg.fr/simbad/sim-id?Ident=51+Eri
        plx = gaia_plx(gaia_id=00000000),
        # M represents the total system mass in solar masses. You can find an accepted value and uncertainty in the paper.
        M   = TruncatedNormal(1.0, 0.5, 0, Inf),
    ),
    # And here we list our planets:
    b
)

## Fit
# This could take ~2-5 minutes.
# If your answer doesn't match the paper, you can try re-running with more
# iteartions (e.g. 20,000 or even 100,000 if you're patient)
chains = DirectDetections.hmc(
    cEri, 0.95,
    adaptation=  1000,
    iterations= 5_000,
    tree_depth=    13,
)
# You can also run multiple chains in parallel - let us know and we can help you
# set that up.

## View summary of results
display(chains)

## Plot fitted model (will take ~1 minute the first time), coloured by ecentricity.
using Plots
plotmodel(chains, color=:e, alpha=0.1)
xlims!(-1000,750)
ylims!(-750,1000)

## Optional: save your figure
savefig("cEri-orbit-posterior.pdf")

## Look at just the semi-major axis histgoram (i.e. the marginal posterior)
histogram(chains["b[a]"])

max_stars_repo_name: sefffal/teps-problems

max_issues_repo_name: sefffal/teps-problems

max_forks_repo_name: sefffal/teps-problems


text:
# ####################### Parameters #############################################
import Base.NamedTuple as Parameter
export Parameter

# import Base.+
# function +(p::Parameter, q::Parameter)
#     names_p = keys(p)
#     names_q = keys(q)
#     new_names_q = []
#     for (i, key) in enumerate(names_q)
#         j = 1
#         while !isempty( intersect(names_p, [key] ) )
#             key = Symbol( string(key) * "_$j")
#             j+=1
#         end
#         println(new_names_q)
#         println(key)
#         new_names_q = (new_names_q..., (key, )...)
#     end

#     merge(Parameter{names_p}(values(p)), Parameter{new_names_q}(values(q)))
# end

## Convert parameter (named tuple) to vector for use in fitting
function parameter_to_vec(p::Parameter)
    l = length(p)
    v = Vector{Float64}()
    for (k, vals) in zip(keys(p), p)
        if typeof(vals) <: AbstractFloat
            append!(v, vals)
        else
            append!(v, vec(vals))
        end
    end
    return v
end

## Convert vector back to parameter for use after fitting
function vec_to_parameter!(p::Parameter, v::Vector{Float64})
    i = 1
    j = 1
    t = Vector{Any}(undef, length(p))
    for val in p
        l = length(val)
        if l == 1
            t[j] = v[i]
            i += 1
        else
            s = size(val)
            t[j] = reshape(v[i:(i+l-1)], s)
            i += l
        end
        j += 1
    end
    p = Parameter{keys(p)}(t)
    return p
end


text:
module DisorderConfigurations

using StaticArrays
using ..PhysicalVectors
using ..ShearFunctions

export DisorderConfiguration, generate_disorder, generate_disorder!

"""
Interface type for different disorder configurations
"""
abstract type DisorderConfiguration end

"""
    generate_disorder!( disorder_field, dis_config::DisorderConfiguration )

Interface function for the `DisorderConfiguration` type with an implication of an in-place modification of the `disorder_field`.
"""
generate_disorder!( disorder_field, dis_config::DisorderConfiguration ) = error("No implementation has been defined for $(typeof(dis_config)).")

"""
    generate_disorder( dis_config::DisorderConfiguration )

Interface function that creates new field to store the result of `generate_disorder!` in.
"""
generate_disorder( dis_config::DisorderConfiguration ) = error("No implementation has been defined for $(typeof(dis_config)).")

mutable struct ShearFromDislocations <: DisorderConfiguration
    vector_diff::Function
    axes::Tuple{Int, Int}
    dislocations::Array{Vector2D, 2}
    strain_fields::SVector{SMatrix}
end

"""
    ShearFromDislocations(; Lx, Ly = Lx)

Convenient keyword constructor for the `ShearFromDislocations` type.
"""
function ShearFromDislocations(; Lx, Ly = Lx, diff )
    axes = (Lx, Ly)
    dislocations = Array{Vector2D, 2}(undef)
    b1g_field = @SMatrix zeros(Float64, ( Lx, Ly ))
    b2g_field = @SMatrix zeros(Float64, ( Lx, Ly ))
    return ShearFromDislocations( diff, axes, dislocations, @SVector [ b1g_field, b2g_field ] )
end

set_dislocations!( sfd::ShearFromDislocations, dislocation_property_array ) = ( sfd.dislocations = dislocation_property_array )

function compute_strains!( sfd::ShearFromDislocations )
    temp_eval_r = Vector2D(0., 0.)
    for (xdx, ydx) ∈ collect( Iterators.product( 1:sfd.Lx, 1:sfd.Ly ) )
        for dis_idx ∈ 1:size(sfd.dislocations[1])
            Vector2D!( temp_eval_r, (xdx, ydx) )
            (b1g, b2g) = bxg_shears!( temp_eval_r, sfd.dislocations[1, dis_idx]; source_r = sfd.dislocations[2, dis_idx], diff = sfd.vector_diff )
            sfd.strain_fields[1][xdx, ydx] += b1g
            sfd.strain_fields[2][xdx, ydx] += b2g
        end
    end
    return nothing
end

function generate_disorder!( disorder_field, sfd::ShearFromDislocations )
    compute_strains!( sfd )
    copyto!( disorder_field, sfd.strain_fields )
    return nothing
end

function generate_disorder( sfd::ShearFromDislocations )
    disorder_fields = @SVector [ @SMatrix zeros(Float64, sfd.axes), @SMatrix zeros(Float64, sfd.axes) ]
    generate_disorder!( disorder_fields, sfd )
    return disorder_fields
end

end


text:
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

################################################################################
# Common types used in MXNet API
################################################################################
const MX_uint = Cuint
const MX_float = Cfloat
const MX_handle = Ptr{Cvoid}

const char_p = Ptr{UInt8}
const char_pp = Ptr{char_p}

################################################################################
# Enumeration from MXNet headers
################################################################################
# OpReqType in include/mxnet/op_attr_types.h
@enum GRAD_REQ GRAD_NOP=0 GRAD_WRITE=1 GRAD_INPLACE=2 GRAD_ADD=3
const grad_req_map = Dict{Symbol,GRAD_REQ}(
    :nop     => GRAD_NOP,      # no operation, do not write anything
    :write   => GRAD_WRITE,    # write gradient to provided space
    :inplace => GRAD_INPLACE,  # perform an inplace write
    :add     => GRAD_ADD,      # add to the provided space
)

################################################################################
# Initialization and library API entrance
################################################################################
const MXNET_LIB = Libdl.find_library(["libmxnet.$(Libdl.dlext)", "libmxnet.so"],  # see build.jl
                                     [joinpath(get(ENV, "MXNET_HOME", ""), "lib"),
                                      joinpath(get(ENV, "MXNET_HOME", ""), "build"),
                                      get(ENV, "MXNET_HOME", ""),
                                      joinpath(@__DIR__, "..",
                                               "deps", "usr", "lib")])
const LIB_VERSION = Ref{Cint}(0)

if isempty(MXNET_LIB)
  # touch this file, so that after the user properly build libmxnet, the precompiled
  # MXNet.ji will be re-compiled to get MXNET_LIB properly.
  touch(@__FILE__)
  error("Cannot find or load libmxnet.$(Libdl.dlext). " *
        "Please see the document on how to build it.")
else
  include_dependency(MXNET_LIB)
end

function __init__()
  # TODO: bug in nnvm, if do not call this, call get handle "_copyto" will fail
  _get_libmx_op_names()
  _populate_iter_creator_cache!()
  _get_lib_version!()

  atexit() do
    # notify libmxnet we are shutting down
    ccall( ("MXNotifyShutdown", MXNET_LIB), Cint, () )
  end
end

function mx_get_last_error()
  msg = ccall( ("MXGetLastError", MXNET_LIB), char_p, () )
  if msg == C_NULL
    throw(MXError("Failed to get last error message"))
  end
  return unsafe_string(msg)
end

"Utility macro to call MXNet API functions"
macro mxcall(f, argtypes, args...)
  args = map(esc, args)
  quote
    _mxret = ccall(($f, $MXNET_LIB),
                   Cint, $(esc(argtypes)), $(args...))
    if _mxret != 0
      err_msg = mx_get_last_error()
      throw(MXError(err_msg))
    end
  end
end

"""
Get libmxnet version

This function will changes the global variable `LIB_VERSION`.
"""
function _get_lib_version!()
  @mxcall :MXGetVersion (Ref{Cint},) LIB_VERSION
  LIB_VERSION[]
end

################################################################################
# Handle types
################################################################################
function mx_define_handle_t(name, destructor)
  @eval begin
    mutable struct $name
      value::MX_handle

      function $name(value = C_NULL)
        hdr = new(value)

        $(if destructor != nothing
          :(finalizer(delete!, hdr))
        end)

        return hdr
      end
    end

    $(if finalizer != nothing
      quote
        function delete!(h :: $name)
          if h.value != C_NULL
            @mxcall($(QuoteNode(destructor)), (MX_handle,), h.value)
            h.value = C_NULL
          end
        end
      end
    end)

    function Base.unsafe_convert(::Type{MX_handle}, obj::$name)
      obj.value
    end
    Base.convert(t::Type{MX_handle}, obj::$name) = Base.unsafe_convert(t, obj)
    Base.cconvert(t::Type{MX_handle}, obj::$name) = Base.unsafe_convert(t, obj)

    MX_handle(x::$name) = Base.convert(MX_handle, x)
  end
end

mx_define_handle_t(:MX_NDArrayHandle,  :MXNDArrayFree)
mx_define_handle_t(:MX_OpHandle,       nothing)
mx_define_handle_t(:MX_SymbolHandle,   :MXSymbolFree)
mx_define_handle_t(:MX_ExecutorHandle, :MXExecutorFree)
mx_define_handle_t(:MX_DataIterHandle, :MXDataIterFree)
mx_define_handle_t(:MX_KVStoreHandle,  :MXKVStoreFree)

################################################################################
# MXNet Params
#
# MXNet API use string to pass some common parameters like the configurations
# when defining layers. Typically, it is enough to use string(obj) to get a
# recognizable representation for libmxnet. However, there is currently a
# caveat:
#
# Because Julia use column-major ordering for tensors. In order to properly
# interact with Julia Arrays, the shape will look "reversed" from the Julia
# side. For example, a typical MNIST mini-batch tensor is of shape (28,28,1,100)
# from Julia side, while the shape information for the same piece of memory
# should be interpreted as (100,1,28,28) from C/C++/Python side.
#
# Therefore, when passing parameters to libmxnet, we should reverse the shape
# parameter. For example, when the user specify a non-square kernel size for
# a convolution or pooling layer. Unfortunately, those operators are automatically
# imported, and information about the type of each parameter is somehow limited.
# One hacky way is to match the type description for the string "Shape(tuple)"
# when importing operators. But currently we simply decided to reverse **all**
# NTuple{N, Int} passed to libmxnet.
#
# TODO: find a better solution in case this cause issues in the future.
# I made `@_remap` in `ndarray.jl`. (Iblis Lin)
################################################################################
dump_mx_param(val::Any)        = string(val)
dump_mx_param(val::Float64)    = @sprintf("%.16e", val)
dump_mx_param(val::Float32)    = @sprintf("%.8e", val)
dump_mx_param(val::Float16)    = @sprintf("%.4e", val)
dump_mx_param(val::Irrational) = @sprintf("%.16e", val)
dump_mx_param(shape::NTuple{N,<:Integer}) where N =
  string(reverse(shape))

"""
A convenient macro copied from Mocha.jl that could be used to define structs
with default values and type checks. For example
```julia
@defstruct MyStruct Any (
  field1 :: Int = 0,
  (field2 :: AbstractString = "", !isempty(field2))
)
```
where each field could be either
```julia
field_name :: field_type = default_value
```
or put within a tuple, with the second element
specifying a validation check on the field value.
In the example above, the default value for
field2 does not satisfy the assertion, this
could be used to force user to provide a
valid value when no meaningful default value
is available.

The macro will define a constructor that could accept
the keyword arguments.
"""
macro defstruct(name, fields)
  _defstruct_impl(false, name, fields)
end

"""A convenient macro to define immutable structs. The same as
`@defstruct` except that the defined type is immutable.
"""
macro defimmutable(name, fields)
  _defstruct_impl(true, name, fields)
end

"""Internal use only, this value is used to indicate a required value
is not specified.
"""
struct __Undefined
end

function _defstruct_impl(is_immutable, name, fields)
  if isa(fields, Expr) && fields.head == :tuple
    fields = fields.args
  else
    fields = [fields]
  end
  @assert length(fields) > 0

  if isa(name, Symbol)
    name       = esc(name)
    super_name = :Any
  else
    @assert(isa(name, Expr) && name.head == :(<:) && length(name.args) == 2 &&
            isa(name.args[1], Symbol) && isa(name.args[2], Symbol),
            "name must be of form 'Name <: SuperType'")

    super_name = esc(name.args[2])
    name       = esc(name.args[1])
  end

  field_defs     = Vector{Expr}(undef, length(fields))  # :(field2 :: Int)
  field_names    = Vector{Expr}(undef, length(fields))  # :field2
  field_defaults = Vector{Expr}(undef, length(fields))  # :(field2 = 0)
  field_types    = Vector{Expr}(undef, length(fields))  # Int
  field_asserts  = Vector{Expr}(undef, length(fields))  # :(field2 >= 0)
  required_field = Symbol[]

  for i = 1:length(fields)
    field = fields[i]
    if field.head == :tuple
      field_asserts[i] = esc(field.args[2])
      field = field.args[1]
    end
    if field.head == :(=)
      fname             = field.args[1].args[1]
      field_defs[i]     = esc(field.args[1])
      field_names[i]    = esc(fname)
      field_types[i]    = esc(field.args[1].args[2])
      field_defaults[i] = Expr(:kw, fname, esc(field.args[2]))
    else
      # no default value provided, required field
      fname             = field.args[1]
      field_defs[i]     = esc(field)
      field_names[i]    = esc(fname)
      field_types[i]    = esc(field.args[2])
      field_defaults[i] = Expr(:kw, fname, __Undefined())
      push!(required_field, fname)
    end
  end

  # body of layer type, defining fields
  type_body = Expr(:block, field_defs...)

  # constructor
  requires = map(required_field) do fname
    :(@assert(!isa($fname, __Undefined), "value for " * string($fname) * " is required"))
  end
  converts = map(zip(field_names, field_types)) do param
    f_name, f_type = param
    :($f_name = convert($f_type, $f_name))
  end
  asserts = map(filter(i -> isassigned(field_asserts,i), 1:length(fields))) do i
    :(@assert($(field_asserts[i])))
  end
  construct = Expr(:call, name, field_names...)
  ctor_body = Expr(:block, requires..., converts..., asserts..., construct)
  ctor_def = Expr(:call, name, Expr(:parameters, field_defaults...))
  ctor = Expr(:(=), ctor_def, ctor_body)

  if is_immutable
    quote
      struct $(name) <: $(super_name)
        $type_body
      end

      $ctor
    end
  else
    quote
      mutable struct $(name) <: $(super_name)
        $type_body
      end

      $ctor
    end
  end
end

max_stars_repo_name: MoisesHer/incubator-mxnet

max_issues_repo_name: MoisesHer/incubator-mxnet

max_forks_repo_name: MoisesHer/incubator-mxnet


text:
macro libarb(function_name)
    return (:($function_name), libarb)
end

argsymbol(x::Symbol) = x
argsymbol(x::Expr) = (@assert x.head == Symbol("::"); argsymbol(first(x.args)))

function digest_call(function_call)
    templates = []
    if function_call.head == :where
        templates = function_call.args[2:end]
        function_call = function_call.args[1]
    end
    @assert function_call.head == :call
    fname = function_call.args[1]
    args = Symbol[ argsymbol(x) for x in function_call.args[2:end]]

    return fname, args, templates
end

function from_inplace(res, fname, args, templates)
    inplace_fname = Symbol(fname, "!")
    if isempty(templates)
        expr = :(
            function $fname($(args...))
                res = $(res)
                return $(inplace_fname)(res, $(args...))
            end
        )
    else
        expr = :(
            function $fname($(args...)) where {$(templates)...}
                res = $(res)
                return $(inplace_fname)(res, $(args...))
            end
        )
    end
    return expr
end

macro frominplace(function_call)
    fname, args, templates = digest_call(function_call)
    @assert !isempty(args)

    if args[1] isa Expr && args[1].head == Symbol("::")
        # the empty constructor of the type of the first argument
        res = :($(args[1].args[2])())
    else
        res = :(typeof($(first(args)))())
    end

    return from_inplace(res, fname, args, templates)
end

macro frominplace(res, function_call)
    fname, args, templates = digest_call(function_call)
    return from_inplace(res, fname, args, templates)
end

macro libcall(name::Symbol, n::Int, opts...)
    inplace = true
    prec = false
    for opt in opts
        if opt isa Expr && opt.head == :(=)
            if opt.args[1] == :inplace
                inplace = opt.args[2]::Bool
            elseif opt.args[1] == :prec
                prec = opt.args[2]::Bool
            else
                throw(UndefKeywordError(opts.args[1]))
            end
        else
            throw(ArgumentError("Unexpected argument: $opt"))
        end
    end

    typename, fname = split(String(name), "_"; limit = 2)
    T = Symbol(titlecase(typename))

    arg_names = Any[esc(Symbol(:x, i)) for i = 1:n]
    arg_types = Symbol[T for _ in 1:n]
    ccall_types = Any[:(Ref{$T}) for _ in 1:n]

    if endswith(fname, "_si")
        fname = chop(fname; tail = 3)
        push!(ccall_types, :Clong)
        push!(arg_names, esc(:e))
        push!(arg_types, :Integer)
    elseif endswith(fname, "_ui")
        fname = chop(fname; tail = 3)
        push!(ccall_types, :Culong)
        push!(arg_names, esc(:e))
        push!(arg_types, :Integer)
    end

    args = map(arg_names, arg_types) do x, T
        :($x::$T)
    end

    if prec
        p = esc(:prec)
        push!(args, Expr(:kw, :($p::Integer), :(precision($(arg_names[1])))))
        push!(ccall_types, :Clong)
        push!(arg_names, p)
    end

    fsymb = inplace ? Symbol(fname, "!") : Symbol(fname)

    quote
        function $(esc(fsymb))($(args...))
            ccall(
                ($(QuoteNode(name)), libarb),
                Cvoid,
                $(Expr(:tuple, ccall_types...)),
                $(arg_names...),
            )
            $(arg_names[1])
        end
    end
end


text:
# Thanks to @iskyd for the motivation to this solution

connections = split.(readlines("input.txt"), "-")

links = Dict{String, Dict}()

for (cave1, cave2) in connections
    if !haskey(links, cave1)
        links[cave1] = Dict(
            "isBig" => lowercase(cave1) != cave1,
            "connections" => []
        )
    end
    if !haskey(links, cave2)
        links[cave2] = Dict(
            "isBig" => lowercase(cave2) != cave2,
            "connections" => []
        )
    end
    push!(links[cave1]["connections"], cave2)
    push!(links[cave2]["connections"], cave1)
end

#! Part 1

function part1()

    frontier = [["start"]]
    count = 0

    while !isempty(frontier)
        current = pop!(frontier)

        for neighbor in links[last(current)]["connections"]
            if !(neighbor in current) || links[neighbor]["isBig"]
                if neighbor == "end"
                    count += 1
                else
                    push!(frontier, push!(copy(current), neighbor))
                end
            end
        end

    end

    return count
end

part1()

#! Part 2

function part2()

    frontier = [["start"]]
    value = 0

    while !isempty(frontier)
        current = pop!(frontier)

        # Check if we've visited the things multiple times
        haveMultipleVisits = false
        visits = Dict([(i, count(x -> x == i, current)) for i in unique(current)])

        for (key, val) in visits
            if !links[key]["isBig"] && val > 1
                haveMultipleVisits = true
                break
            end
        end

        for neighbor in links[last(current)]["connections"]
            if neighbor == "start"
                continue
            end
            if !(neighbor in current) || links[neighbor]["isBig"] || !haveMultipleVisits
                if neighbor == "end"
                    value += 1
                else
                    push!(frontier, push!(copy(current), neighbor))
                end
            end
        end

    end

    return value
end

part2()

max_stars_repo_path: Day 12/main.jl

max_issues_repo_path: Day 12/main.jl

max_forks_repo_path: Day 12/main.jl


text:
#
# 2D Triangulations
# =================
#
# These examples can be loaded into Julia (Revise.jl recommended)
#
# These examples return either a [`SimplexGridBuilder`](@ref) struct which can
# be turned into a grid by calling [`simplexgrid`](@ref)(builder), or
# just an ExtendableGrid with default simplex grid data.
#
# The control flags for [Triangle](https://github.com/JuliaGeometry/Triangulate.jl)
# are created based on default options
# provided by this module which try to ensure "good" grids for
# FEM and FVM computations. These are documented in [`default_options`](@ref)
# Occasional [`options!`](@ref) statements in the examples overwrite these defaults.
#
#
# This test code is released under the license conditions of
# Triangulate.jl
#

using SimplexGridFactory
using ExtendableGrids
using LinearAlgebra
using Triangulate

# ## Domain triangulation
# Here we just describe a domain as a polygon and
# mesh it.
#
function triangulation_of_domain()

    builder=SimplexGridBuilder(Generator=Triangulate)

    p1=point!(builder,0,0)
    p2=point!(builder,1,0)
    p3=point!(builder,1,2)
    p4=point!(builder,0,1)
    p5=point!(builder,-1,2)

    facet!(builder,p1,p2)
    facet!(builder,p2,p3)
    facet!(builder,p3,p4)
    facet!(builder,p4,p5)
    facet!(builder,p5,p1)

    builder
end

#
# ![](triangulation_of_domain.svg)
#

#
# ## Triangulation with size control and boundary markers
# The previous example was a little bit bland.
# We miss:
# - size control for the triangles
# - differently marking of boundary parts
#
function nicer_triangulation_of_domain()

    builder=SimplexGridBuilder(Generator=Triangulate)

    p1=point!(builder,0,0)
    p2=point!(builder,1,0)
    p3=point!(builder,1,2)
    p4=point!(builder,0,1)
    p5=point!(builder,-1,2)

    facetregion!(builder,1)
    facet!(builder,p1,p2)
    facet!(builder,p2,p3)
    facetregion!(builder,2)
    facet!(builder,p3,p4)
    facet!(builder,p4,p5)
    facetregion!(builder,3)
    facet!(builder,p5,p1)

    options!(builder,maxvolume=0.01)

    builder
end
#
# ![](nicer_triangulation_of_domain.svg)
#

# ## Triangulation with subregions
# Here we create different subregions and
# apply the maxvolume constraint to
# the subregions
#
function triangulation_of_domain_with_subregions()

    builder=SimplexGridBuilder(Generator=Triangulate)

    p1=point!(builder,0,0)
    p2=point!(builder,1,0)
    p3=point!(builder,1,2)
    p4=point!(builder,0,1)
    p5=point!(builder,-1,2)

    facetregion!(builder,1)
    facet!(builder,p1,p2)
    facet!(builder,p2,p3)
    facetregion!(builder,2)
    facet!(builder,p3,p4)
    facet!(builder,p4,p5)
    facetregion!(builder,3)
    facet!(builder,p5,p1)

    facetregion!(builder,4)
    facet!(builder,p1,p4)

    cellregion!(builder,2)
    maxvolume!(builder, 0.1)
    regionpoint!(builder, -0.1,0.5)

    cellregion!(builder,3)
    maxvolume!(builder, 0.01)
    regionpoint!(builder, 0.2,0.2)

    builder
end
#
# ![](triangulation_of_domain_with_subregions.svg)
#

# ## Direct specification of input arrays
# Of course we can specify the input for Triangle
# directly. The aim of SimplexBuilder is to avoid
# the tedious and error prone counting connected
# with this approach.
function direct_square(Generator=Triangulate)
    simplexgrid(Generator;
                points=[0 0 ; 0 1 ; 1 1 ; 1 0]',
                bfaces=[1 2 ; 2 3 ; 3 4 ; 4 1 ]',
                bfaceregions=[1, 2, 3, 4],
                regionpoints=[0.5 0.5;]',
                regionnumbers=[1],
                regionvolumes=[0.01])
end
#
# ![](direct_square.svg)
#

# ## Local refinement
#
# We can interface to Triangle's unsuitable mechanism
#
function square_localref()

    builder=SimplexGridBuilder(Generator=Triangulate)
    cellregion!(builder,1)
    maxvolume!(builder,0.01)
    regionpoint!(builder,0.5,0.5)

    p1=point!(builder,0,0)
    p2=point!(builder,1,0)
    p3=point!(builder,1,1)
    p4=point!(builder,0,1)

    facetregion!(builder,1)
    facet!(builder,p1,p2)
    facetregion!(builder,2)
    facet!(builder,p2,p3)
    facetregion!(builder,3)
    facet!(builder,p3,p4)
    facetregion!(builder,4)
    facet!(builder,p4,p1)

    refinement_center=[0.5,0.5]
    function unsuitable(x1,y1,x2,y2,x3,y3, area)
        bary=[(x1+x2+x3)/3,(y2+y2+y3)/3]
        dist=norm(bary-refinement_center)
        if area > 0.01*dist
            return 1
        else
            return 0
        end
    end
    options!(builder, unsuitable=unsuitable)
    builder
end
#
# ![](square_localref.svg)
#

# ## Domain with holes
# We can generate domains with holes.
# This at once shall demonstrate how the chosen
# API approach eases bookeeping of features added to the
# geometry description
#
function swiss_cheese_2d()

    function circlehole!(builder, center, radius; n=20)
        points=[point!(builder, center[1]+radius*sin(t),center[2]+radius*cos(t)) for t in range(0,2π,length=n)]
        for i=1:n-1
            facet!(builder,points[i],points[i+1])
        end
        facet!(builder,points[end],points[1])
        holepoint!(builder,center)
    end

    builder=SimplexGridBuilder(Generator=Triangulate)
    cellregion!(builder,1)
    maxvolume!(builder,0.1)
    regionpoint!(builder,0.1,0.1)

    p1=point!(builder,0,0)
    p2=point!(builder,10,0)
    p3=point!(builder,10,10)
    p4=point!(builder,0,10)

    facetregion!(builder,1)
    facet!(builder,p1,p2)
    facet!(builder,p2,p3)
    facet!(builder,p3,p4)
    facet!(builder,p4,p1)

    holes=[8.0 4.0;
           1.0 2.0;
           8.0 9.0;
           3.0 4.0;
           4.0 6.0;
           7.0 9.0;
           4.0 7.0;
           7.0 5.0;
           2.0 1.0;
           4.0 1.0;
           4.0 8.0;
           2.0 8.0;
           3.0 6.0;
           4.0 9.0;
           9.0 1.0;
           9.0 1.0;
           6.0 9.0;
           8.0 9.0;
           3.0 5.0;
           1.0 4.0]'

    radii=[0.15, 0.15, 0.1, 0.35, 0.2, 0.3, 0.1, 0.4, 0.1, 0.4, 0.4, 0.15, 0.2, 0.2, 0.2, 0.35, 0.15, 0.25, 0.15, 0.25]

    for i=1:length(radii)
        facetregion!(builder,i+1)
        circlehole!(builder,holes[:,i], radii[i])
    end

    builder
end
#
# ![](swiss_cheese_2d.svg)
#


text:
module AnalyticalFieldsTests

using Test
using Gridap
import Gridap: ∇

p1 = Point(1.0,2.0)
p2 = Point(0.0,3.0)
p3 = Point(7.0,4.0)
p4 = Point(8.0,1.0)
p = [p1,p2,p3,p4]

fun(x) = 3*x[1]

f = AnalyticalField(fun,2)
v = fun.(p)
test_field_without_gradient(f,p,v)

fungrad(x) = VectorValue(3.0,0.0)

∇(::typeof(fun)) = fungrad

f = AnalyticalField(fun,2)
v = fun.(p)
g = fungrad.(p)
test_field(f,p,v,g)

end # module


text:
"""    asymptotics(S; rtol = eps())

Computes the ergodic parameters and asymptotic value of the memory kernel of
the Generalized Langevin Equation.  The precision of the method can be
controlled by the keyword argument `rtol`, which sets the relative tolerance
for asymptotic value ot the memory function, `ζ∞`.
"""
function asymptotics(S; rtol = eps(), verbose = false)
    # Dynamical variables, memory kernel variables and auxiliar variables
    avars, kvars, D₀ = initialize_asymptotics(S)
    Z = similar(kvars.svars.w)
    g = FixedPoint(Z, kvars, D₀)

    return asymptotics!(g, avars, avars.ζ∞[]; rtol = rtol, verbose = verbose)
end

function asymptotics!(g, avars, ζ; rtol = eps(), verbose = false)
    # Initialize Anderson Acceleration (AA). We use only the previous point,
    # that is, we set `m = 1` in the AA algorithm.
    # TODO: Try AA with `m = 2`.
    z₋ = g(ζ)
    z̃₋ = g(z₋)
    f₋ = z₋ - z̃₋
    z = z̃₋
    z̃ = g(z)
    f = z - z̃

    # Counter to keep track of how many times the sequence regresses whenever
    # `f₋, f` are positive.
    c = 0
    # AA step and reference value to check when the sequence regresses.
    Δz = Δz̃ = zero(z)
    # A trust region is used to ensure positive-definiteness of the solution.
    trustregion = EllipsoidalRegion()

    while true
        if isapprox(z, z̃; rtol = rtol, nans = true) || isapprox(z₋, z; rtol = rtol, nans = true)
            break
        end

        Δf = f - f₋
        Δf² = Δf ⋅ Δf
        if iszero(Δf²)
            Δz = zero(Δz)
        else
            γ = (Δf ⋅ f) / Δf²
            Δz = bound(trustregion, z̃, γ * (z̃ - z̃₋))
        end

        if eachisless(Δz, 0) && eachisless(0, f) && eachisless(0, f₋)
            if c < 1
                Δz̃ = Δz
            elseif (c ≥ 8 && eachisless(Δz, Δz̃)) || c > 16
                z = f = zero(z)
                break
            end
            c += 1
        end

        z₋ = z
        z̃₋ = z̃
        f₋ = f
        z = z̃ - Δz
        z̃ = g(z)
        f = z - z̃

        verbose && @show z, f
    end

    return set!(avars, g.Z, g.kvars, g.D₀, z)
end

function asymptotic_mobility!(b, b′, ζ, dvars, kvars, auxvars, Δτ, n₀, n, rtol, brtol)
    if !anyiszero(ζ[])
        return b[] = zero(b[])
    end

    while true
        Δτ *= 2
        b′ = b

        decimate!(dvars)
        solve!(dvars, kvars, auxvars, Δτ, n₀, n, rtol)
        b[] = b[] + integrate(Δτ, view(dvars.ζ, (n₀ - 1):n))

        if isapprox(b′[], b[]; rtol = brtol, nans = true)
            break
        end
    end

    return b[] = inv(b[])
end


text:
module BenchSweeps

export BenchSweepGroup, defsweep!, @defsweep!

using Reexport: @reexport
using Requires: @require

@reexport using BenchmarkTools
using JSON
using MacroTools: @capture
using Statistics

include("iterutils.jl")

const array_names = (:gctime, :time)
const reserved_names = (array_names..., :alloc, :memory, :bench, :name)

const NameType = Any

"""
    BenchSweepGroup()

# Examples
```jldoctest
julia> suite = BenchSweepGroup();

julia> suite.axes[:n] = 2 .^ (0:2:10);

julia> suite.axes[:f] = [:rand, :randn];

julia> using Random

julia> @defsweep! for (n, f) in suite["sort"]
           @benchmarkable sort(x) setup=(x = Random.\$f(\$n))
       end;
```

# Internals
- `axes::Dict{Symbol,Vector}`: A mapping `axis key -> axis values`.
- `sweeps::Dict{Symbol,Vector{Symbol}}`: A mapping from benchmark group
  name to a list of axis keys.
- `bench::BenchmarkGroup`: A mapping `name -> coord -> benchark or trial`.
  For a `name`, `coord` is a `n`-tuple where `n = length(sweeps[name])`.
  The `i`-th element of `coord` is taken from `axes[sweeps[name][i]]`.
"""
struct BenchSweepGroup
    axes::Dict{Symbol,Vector}
    sweeps::Dict{NameType,Vector{Symbol}}
    bench::BenchmarkGroup
end

BenchSweepGroup() = BenchSweepGroup(Dict(), Dict(), BenchmarkGroup())

function check_axes_key(axes_keys)
    for key in axes_keys
        @assert key ∉ reserved_names
    end
end

iter_coords(group, name) =
    Iterators.product([group.axes[k] for k in group.sweeps[name]]...)

function defsweep!(f::Function, group::BenchSweepGroup,
                   name::NameType,
                   axes_keys::Vector{Symbol})
    check_axes_key(axes_keys)
    @assert !haskey(group.sweeps, name)
    group.sweeps[name] = axes_keys
    group.bench[name] = suite = BenchmarkGroup()
    for coord in iter_coords(group, name)
        suite[coord] = f(coord...)
    end
    return suite
end

macro defsweep!(for_loop)
    if !@capture(for_loop, for axes_keys_ in group_[name_]; body_; end)
        error("Unsupported syntax:\n", for_loop)
    end
    if axes_keys isa Symbol
        axes_keys = Symbol[axes_keys]
    elseif axes_keys isa Expr
        axes_keys = Symbol.(axes_keys.args)
    else
        error("Unsupported `axes_keys` expression:\n", axes_keys)
    end
    esc(quote
        $defsweep!($group, $name, $axes_keys) do $(axes_keys...)
            $body
        end
    end)
end

BenchmarkTools.tune!(group::BenchSweepGroup, args...; kwargs...) =
    tune!(group.bench, args...; kwargs...)

Base.run(group::BenchSweepGroup, args...; kwargs...) =
    BenchSweepGroup(group.axes,
                    group.sweeps,
                    run(group.bench, args...; kwargs...))

"""
    BenchSweeps.save(filename, group::BenchSweepGroup)
    BenchSweeps.load(filename) :: BenchSweepGroup

Save/load `BenchSweepGroup` to/from a JSON file.
"""
(save, load)

function json_friendly_bench(group, ::Val{tofriendly}) where {tofriendly}
    bench = BenchmarkGroup()
    for name in keys(group.sweeps)
        bench[name] = sub = BenchmarkGroup()
        for (i, coord) in enumerate(iter_coords(group, name))
            if tofriendly
                sub[string(i)] = group.bench[name][coord]
            else
                sub[coord] = group.bench[name][string(i)]
            end
        end
    end
    return bench
end

function save(io::IO, group::BenchSweepGroup)
    print(io, """{"axes":""")
    JSON.print(io, group.axes)
    print(io, ""","sweeps":""")
    JSON.print(io, group.sweeps)
    print(io, ""","bench":""")
    BenchmarkTools.save(io, json_friendly_bench(group, Val(true)))
    print(io, "}")
    return
end

recover_axes(dict) = Dict{Symbol,Vector}((Symbol(k) => v) for (k, v) in dict)
recover_sweeps(dict) =
    Dict{NameType,Vector{Symbol}}((k => Symbol.(v)) for (k, v) in dict)

function recover_bench(parsed)
    @assert length(parsed) == 2
    versions = parsed[1]::Dict  # not used in BenchmarkTools ATM
    values = parsed[2]::Vector
    @assert length(values) == 1
    return map!(BenchmarkTools.recover, values, values)[1]
end

function load(io::IO)
    parsed = JSON.parse(io)
    @assert keys(parsed) == Set(String.(fieldnames(BenchSweepGroup)))
    tmp = BenchSweepGroup(
        recover_axes(parsed["axes"]),
        recover_sweeps(parsed["sweeps"]),
        recover_bench(parsed["bench"]),
    )
    group = BenchSweepGroup(
        tmp.axes,
        tmp.sweeps,
        json_friendly_bench(tmp, Val(false)),
    )
    if keys(group.sweeps) != keys(group.bench)
        @warn """
        Loaded `BenchSweepGroup` is inconsistent.
        Unsatisfied: keys(group.sweeps) == keys(group.bench)
        """
    end
    return group
end

function save(filename::AbstractString, group::BenchSweepGroup)
    endswith(filename, ".json") ||
        throw(ArgumentError("Only JSON serialization is supported."))
    open(filename, "w") do io
        save(io, group)
    end
end

function load(filename::AbstractString)
    endswith(filename, ".json") ||
        throw(ArgumentError("Only JSON serialization is supported."))
    return open(filename, "r") do io
        load(io)
    end
end

"""
    BenchSweeps.astable(group, [agg = median])
    BenchSweeps.asrawtable(group)
    BenchSweeps.asbenchtable(group)

Convert `group::BenchSweepGroup` to an iterator of `NamedTuple`s.
This is exposed as Tables.jl and TableTraits.jl interfaces.
This function can be invoked by `DataFrames.DataFrame` as well.

# Arguments

- `agg::Union{Function,Symbol}`: "aggregation" function.  Any
  function that consumes `BenchmarkTools.Trial` and yields
  `BenchmarkTools.TrialEstimate` can be passed (e.g., `median`,
  `minimum`, `mean`, etc.).

  If it is `:raw` then this function returns a longer iterator whose
  element is an individual sample.  `asrawtable(group)` is an alias
  for `astable(group, :raw)`.

  If it is `:bench` then each row includes `BenchmarkTools.Trial`
  object in `:bench` column.  This is useful for extracting trial
  results and run `judge` on them.  `asbenchtable(group)` is an alias
  for `astable(group, :bench)`.  Note that this option works also when
  `group.bench` holds `BenchmarkTools.Benchmark` objects instead of
  trials.

"""
(astable, asrawtable, asbenchtable)

function astable(group::BenchSweepGroup, agg = median)
    if agg === :raw
        asrawtable(group)
    elseif agg === :bench
        asbenchtable(group)
    elseif agg isa Union{Symbol, AbstractString}
        # capture a typo here:
        error("`agg` must be `:raw`, `:bench` or a callable; got: ", agg)
    else
        # otherwise let's hope that `agg` is a callable
        _astable(group, agg)
    end
end

asrawtable(group::BenchSweepGroup) = _astable(group, nothing)

function _preprocess_table(group)
    axes_keys = sort(collect(keys(group.axes)))
    eltypes = [
        let T = eltype(group.axes[key])
            if T === Any
                T
            elseif all(axs -> T in axs, values(group.sweeps))
                T
            else
                Union{Missing, T}
            end
        end for key in axes_keys
    ]

    # name -> coord -> position in sorted axes_keys
    keytopos = Dict{NameType,Dict{Symbol,Int}}()
    for name in keys(group.bench)
        keytopos[name] = Dict()
        for k in group.sweeps[name]
            keytopos[name][k] = findfirst(isequal(k), axes_keys)
        end
    end

    return (axes_keys, eltypes, keytopos)
end

function _astable(group::BenchSweepGroup, agg)
    axes_keys, eltypes, keytopos = _preprocess_table(group)
    colnames = [:name; axes_keys; [:allocs, :gctime, :memory, :time]]
    coleltypes = [NameType; eltypes; [Int, Float64, Int, Float64]]
    rowtype = NamedTuple{Tuple(colnames), Tuple{coleltypes...}}

    if agg !== nothing
        return TypedGenerator{rowtype}(SizedIterator(
            (name, coord)
            for name in keys(group.bench) for coord in iter_coords(group, name)
        )) do (name, coord)
            trial = group.bench[name][coord]
            estimate = agg(trial)

            vals = Vector(undef, length(group.axes))
            fill!(vals, missing)
            for (k, v) in zip(group.sweeps[name], coord)
                vals[keytopos[name][k]] = v
            end

            return rowtype((
                name,
                vals...,
                estimate.allocs,
                estimate.gctime,
                estimate.memory,
                estimate.time,
            ))
        end
    else
        return TypedGenerator{rowtype}(SizedIterator(
            (name, coord, time, gctime)
            for name in keys(group.bench)
            for coord in iter_coords(group, name)
            for (time, gctime) in let trial = group.bench[name][coord]
                                      zip(trial.times, trial.gctimes)
                                  end
        )) do (name, coord, time, gctime)
            trial = group.bench[name][coord]

            vals = Vector(undef, length(group.axes))
            fill!(vals, missing)
            for (k, v) in zip(group.sweeps[name], coord)
                vals[keytopos[name][k]] = v
            end

            return rowtype((
                name,
                vals...,
                trial.allocs,
                gctime,
                trial.memory,
                time,
            ))
        end
    end
end

# Type without type parameters
constructor_of(@nospecialize(_::T)) where {T} =
    getproperty(parentmodule(T), nameof(T))

function asbenchtable(group::BenchSweepGroup)
    axes_keys, eltypes, keytopos = _preprocess_table(group)
    bench = first(first(values(g)) for g in values(group.bench))
    benchtype = constructor_of(bench)
    colnames = [:name; axes_keys; [:bench]]
    coleltypes = [NameType; eltypes; [benchtype]]
    rowtype = NamedTuple{Tuple(colnames), Tuple{coleltypes...}}

    return TypedGenerator{rowtype}(SizedIterator(
        (name, coord)
        for name in keys(group.bench) for coord in iter_coords(group, name)
    )) do (name, coord)
        trial = group.bench[name][coord]

        vals = Vector(undef, length(group.axes))
        fill!(vals, missing)
        for (k, v) in zip(group.sweeps[name], coord)
            vals[keytopos[name][k]] = v
        end

        return rowtype((
            name,
            vals...,
            trial,
        ))
    end
end

Base.summary(io::IO, group::BenchSweepGroup) =
    print(io, "BenchSweepGroup with ", length(group.axes), " axes",
          " and ", length(group.sweeps), " sweeps")

Base.show(io::IO, group::BenchSweepGroup) = summary(io, group)

function Base.show(io::IO, ::MIME"text/plain", group::BenchSweepGroup)
    summary(io, group)
    println(io)
    printstyled(io, "axes"; color=:blue)
    print(io, " = ")
    show(io, MIME("text/plain"), group.axes)
    println(io)
    printstyled(io, "bench"; color=:blue)
    print(io, " = ")
    show(io, MIME("text/plain"), group.bench)
end

import Tables
Tables.istable(::Type{<:BenchSweepGroup}) = true
Tables.rowaccess(::Type{<:BenchSweepGroup}) = true
Tables.rows(group::BenchSweepGroup) = astable(group)

import TableTraits
import IteratorInterfaceExtensions
TableTraits.isiterabletable(::BenchSweepGroup) = true
IteratorInterfaceExtensions.getiterator(group::BenchSweepGroup) = astable(group)

function __init__()
    @require DataFrames="a93c6f00-e57d-5684-b7b6-d8193f3e46c0" begin
        """
            DataFrame(group::BenchSweepGroup, [agg = median])

        A shorthand for calling `DataFrame(BenchSweeps.astable(group, agg))`.

        See also: [`BenchSweeps.astable`](@ref).
        """
        DataFrames.DataFrame(group::BenchSweepGroup, agg = median) =
            DataFrames.DataFrame(astable(group, agg))
    end
end

end # module


text:
using Documenter
using CuboidGrids

Documenter.makedocs(
    format = Documenter.HTML(
        prettyurls = get(ENV, "CI", nothing)== "true"
    ),
    sitename = "CuboidGrids.jl",
    pages = [
        "Home"=> "index.md",
        "CuboidGrids"=> "CuboidGrids.md",
        "largrid.jl"=> "largrid.md"
    ]
)


text:
module JuliaCon

using Base: String, alignment
using Distributed
using Dates: Dates, Date, DateTime, Time, Hour, Minute
using JSON3
using UrlDownload
using PrettyTables

const CONFERENCE_SCHEDULE_JSON_URL = "https://pretalx.com/juliacon2020/schedule/export/schedule.json"
const conf_json = Ref{JSON3.Object{Vector{UInt8}, SubArray{UInt64, 1, Vector{UInt64}, Tuple{UnitRange{Int64}}, true}}}()

include("countries.jl")
include("schedule.jl")
include("tshirtcode.jl")

function __init__()
    if isdefined(Main, :Distributed)
        env = Base.active_project()
        @everywhere env = $env

        # activate local env of the master process on all the workers
        @eval Main @everywhere begin
            if myid() != 1
                # suppress REPL warnings and "activating ..." messages
                using Pkg, Logging
                with_logger(NullLogger()) do
                    Pkg.activate(env, io=devnull)
                end
            end
        end
        # load JuliaCon.jl on all workers
        @eval Main @everywhere using JuliaCon
    end
end

export juliacon2021, now, today

end