swh:1:snp:a72e953ecd624a7df6e6196bbdd05851996c5e40
Raw File
Tip revision: 3cd6e2675dbaa3119bd0ef7777c445e4bc892978 authored by Rafael Fourquet on 02 January 2015, 08:34:56 UTC
use `Bit` type to create a BitArray (in ones, zeros, rand)
Tip revision: 3cd6e26
github.jl
module GitHub

import Main, ..Git, ..Dir

const AUTH_NOTE = "Julia Package Manager"
const AUTH_DATA = Dict{Any,Any}(
    "scopes" => ["repo"],
    "note" => AUTH_NOTE,
    "note_url" => "http://docs.julialang.org/en/latest/manual/packages/",
)

function user()
    if !success(`git config --global github.user`)
        error("""
        no GitHub user name configured; please configure it with:

            git config --global github.user USERNAME

        where USERNAME is replaced with your GitHub user name.
        """)
    end
    readchomp(`git config --global github.user`)
end

function json()
    isdefined(:JSON) || try require("JSON")
    catch err
        warn(err)
        error("using the GitHub API requires having the JSON package installed ")
    end
    Main.JSON
end

function curl(url::AbstractString, opts::Cmd=``)
    success(`curl --version`) || error("using the GitHub API requires having `curl` installed")
    out, proc = open(`curl -i -s -S $opts $url`,"r")
    head = readline(out)
    status = int(split(head,r"\s+";limit=3)[2])
    header = Dict{AbstractString,AbstractString}()
    for line in eachline(out)
        if !ismatch(r"^\s*$",line)
            (k,v) = split(line, r":\s*"; limit=2)
            header[k] = v
            continue
        end
        wait(proc); return status, header, readall(out)
    end
    error("strangely formatted HTTP response")
end
curl(url::AbstractString, data::Void, opts::Cmd=``) = curl(url,opts)
curl(url::AbstractString, data, opts::Cmd=``) =
    curl(url,`--data $(sprint(io->json().print(io,data))) $opts`)

function delete_token()
    tokfile = Dir.path(".github","token")
    Base.rm(tokfile)
    info("Could not authenticate with existing token. Deleting token and trying again.")
end

function token(user::AbstractString=user())
    tokfile = Dir.path(".github","token")
    isfile(tokfile) && return strip(readchomp(tokfile))
    status, header, content = curl("https://api.github.com/authorizations",AUTH_DATA,`-u $user`)
    tfa = false

    # Check for two-factor authentication
    if status == 401 && get(header, "X-GitHub-OTP", "") |> x->beginswith(x, "required") && isinteractive()
        tfa = true
        info("Two-factor authentication in use.  Enter auth code.  (You may have to re-enter your password.)")
        print(STDERR, "Authentication code: ")
        code = readline(STDIN) |> chomp
        status, header, content = curl("https://api.github.com/authorizations",AUTH_DATA,`-H "X-GitHub-OTP: $code" -u $user`)
    end

    if status == 422
        error_code = json().parse(content)["errors"][1]["code"]
        if error_code == "already_exists"
            if tfa
                info("Retrieving existing GitHub token. (You may have to re-enter your password twice more.)")
                status, header, content = curl("https://api.github.com/authorizations",AUTH_DATA,`-u $user`)
                status != 401 && error("$status: $(json().parse(content)["message"])")
                print(STDERR, "New authentication code: ")
                code = readline(STDIN) |> chomp
                status, header, content = curl("https://api.github.com/authorizations",`-H "X-GitHub-OTP: $code" -u $user`)
            else
                info("Retrieving existing GitHub token. (You may have to re-enter your password.)")
                status, header, content = curl("https://api.github.com/authorizations", `-u $user`)
            end
            (status >= 400) && error("$status: $(json().parse(content)["message"])")
            for entry in json().parse(content)
                if entry["note"] == AUTH_NOTE
                    tok = entry["token"]
                    break
                end
            end
        else
            error("GitHub returned validation error (422): $error_code: $(json().parse(content)["message"])")
        end
    else
        (status != 401 && status != 403) || error("$status: $(json().parse(content)["message"])")
        tok = json().parse(content)["token"]
    end

    mkpath(dirname(tokfile))
    open(io->println(io,tok),tokfile,"w")
    return tok
end

function req(resource::AbstractString, data, opts::Cmd=``)
    url = "https://api.github.com/$resource"
    status, header, content = curl(url,data,`-u $(token()):x-oauth-basic $opts`)
    response = json().parse(content)
    status, response
end

GET(resource::AbstractString, data, opts::Cmd=``) = req(resource,data,opts)
HEAD(resource::AbstractString, data, opts::Cmd=``) = req(resource,data,`-I $opts`)
PUT(resource::AbstractString, data, opts::Cmd=``) = req(resource,data,`-X PUT $opts`)
POST(resource::AbstractString, data, opts::Cmd=``) = req(resource,data,`-X POST $opts`)
PATCH(resource::AbstractString, data, opts::Cmd=``) = req(resource,data,`-X PATCH $opts`)
DELETE(resource::AbstractString, data, opts::Cmd=``) = req(resource,data,`-X DELETE $opts`)

for m in (:GET,:HEAD,:PUT,:POST,:PATCH,:DELETE)
    @eval $m(resource::AbstractString, opts::Cmd=``) = $m(resource,nothing,opts)
end

function pushable(owner::AbstractString, repo::AbstractString, user::AbstractString=user())
    status, response = HEAD("repos/$owner/$repo")
    status == 404 && error("repo $owner/$repo does not exist")
    status, response = GET("repos/$owner/$repo/collaborators/$user")
    status == 204 && return true
    status == 404 && return false
    error("unexpected API status code: $status – $(response["message"])")
end

function fork(owner::AbstractString, repo::AbstractString)
    status, response = POST("repos/$owner/$repo/forks")
    if status == 401
        delete_token()
        status, response = POST("repos/$owner/$repo/forks")
    end
    status == 202 || error("forking $owner/$repo failed: $(response["message"])")
    return response
end

end # module
back to top