Writing JavaScript natives to call the GitHub API


#1

I saw an example showing that the GitHub API had been enabled to support the CORS protocol, and successfully managed to do a JavaScript fetch() of a file blob from it. It was very cool when it worked--especially since the Base64 decoding I used was libRebol!

It turns out that wasn't actually necessary: What I didn't know was that GitHub had enabled fetch() on the raw.github.com family of URLs too (!) So just using those URLs is much cleaner.

But it was a good thing to test and get working. And I'm sure people will be wanting to make GitHub API requests of other kinds. So here's the function I had. It has a spec that's a Rebol block, and can be called from Rebol...but which has a body of JavaScript (that also calls back into Rebol via the libRebol JS API...!)

github-read: js-awaiter [
    owner [text!]
    repo [text!]  ; !!! TBD: branch?
    path [text!]
]{
    let owner = reb.Spell(reb.ArgR('owner'))
    let repo = reb.Spell(reb.ArgR('repo'))
    let path = reb.Spell(reb.ArgR('path'))

    let url = "https://api.github.com/repos/" + owner + "/" + repo
        + "/contents" + path

    let response = await fetch(url)
    if (!response.ok)  // https://www.tjvantoll.com/2015/09/13/fetch-and-errors/
        throw Error(response.statusText)

    let json = await response.json()  // GitHub gives Base64 in JSON envelope

    return reb.Promise("debase/base", reb.T(json.content), reb.I(64))  // see note
}

Note: The actual way you have to return that "promise" at this exact moment isn't as pretty, but that's what I'm working on. Current code makes you write it as a function that calls reb.Run()

return function () {
    return reb.Run("debase/base", reb.T(json.content), reb.I(64))
} 

Despite needing to perform various I/O operations that need to unblock the stack, it will not return to the Rebol caller until the final result is finished. It's a tricky process to orchestrate and do the bookkeeping of threads to allow the back and forth to make it all happen. And when threads are NOT available, it's very tricky too!

(But the absolute trickiest bit is writing one codebase that can be built both ways, and does all that management in a non-invasive way to the Rebol core!)

Another piece of this puzzle was the code that transformed URLs, which was just a little parse rule. It shows the use of :(...) as a way of running code and then splicing the result of the rule in. If it's null, it vaporizes. (There's no /blob in the raw URLs)

parse url [
    ["https://github.com/" (raw: false) | "https://raw.github.com" (raw: true)]
    copy owner: to "/" skip
    copy repo: to "/" skip
    :(if not raw ["blob/"])
    copy branch: to "/"
    copy path: to end  ; include the leading /
] else [
    ; Not a GitHub file URL
]

#2

For reference, here are some APIs that will be interesting to call on GitHub:

It would be pretty neat to see a syntax that could do this; perhaps parsing out the raw or blob URLs as I've shown. What if you could provide your login credentials and actually say in the ReplPad:

 >> blob-url: https://github.com/metaeducation/ren-c/blob/master/README.md

 >> do/args <git> [username: "hostilefork" password: <prompt>]

 >> data: as text! git/read blob-url  ; Triggers GitHub login credential page

 >> replace/all data "Ren-C" "Ren-C-and-JS"

 >> git/commit blob-url "Sample Update Commit"

Anyone want to give that a try? @johnk? :stuck_out_tongue:

It could be written on the desktop first, and then--maybe, just maybe it would work in-browser too.