Cloning From GitHub With One Key in Conkeror

In the past I wrote about how I setup Conkeror so that I could clone a repository from GitHub with only a few key-presses. However, GitHub’s latest user-interface change screwed up all of that. So today I want to show you the page mode I wrote for GitHub, even though Conkeror comes with one, which allows me to clone a project from GitHub simply by hitting C on my keyboard.

Prerequisites

The command we’ll write for cloning a repository will ultimately invoke an external program. That program will be a shell script which accepts one argument: the URI of the repository to clone. So we need to create that before doing anything else. The script can be as simple as this:

#!/bin/sh

REPOSITORY_URI=${1:?"Missing required URI"}

cd /tmp &&
git clone "$REPOSITORY_URI" &&
exit 0

The script I actually use does nothing more except change to a different directory depending on the day of the week. In any case, we will want a script that will accept a URI as an argument and run git-clone on that URI in a place where we want to save our clones from GitHub.

I will refer to this script as /home/eric/Scripts/git-clone-from-uri in the rest of the article.

The Skeleton of Our Page Mode

We begin by creating the file github.js wherever you keep your Conkeror configuration files, e.g. $HOME/.conkerorrc/github.js:

require("content-buffer.js");

define_keymap("github_keymap", $display_name = "github");

//
//    This is where our specific logic will go later
//

define_keymaps_page_mode("github-mode",
    build_url_regexp($domain = "github", $allow_www = true),
    { normal: github_keymap },
    $display_name = "Github");

page_mode_activate(github_mode);

provide("github");

With this in place we can add require("github"); to our Conkeror configuration. The page mode currently does nothing. But let’s begin by examining what we have in place anyways.

We will always want to require content-buffer.js for page modes. Any mode we write will need some of the functions it provides. And any page mode is a natural extension of Conkeror’s default behavior for content buffers.

Page modes often create their own keymaps to setup new key-bindings or replace default ones with mode-specific behavior. Therefore we have this early in the code:

define_keymap("github_keymap", $display_name = "github");

This creates a keymap with the name github_keymap which we will use later with define_key() and related functions. Conkeror does not require anything besides the keymap name but the optional $display_name parameter is useful as a measure of self-documentation.

Sometimes the only reason we want to create a page mode is so that we can create some key-bindings for a specific site. This article provides an example of such a mode: the ultimate goal is to setup one key for cloning GitHub repositories. Conkeror provides define_keymaps_page_mode() to facilitate the creation of such modes. The official Conkeror wiki explains the function but we should take care to note build_url_regexp(). When creating a page mode we need to tell Conkeror when to enable the mode, i.e. on what site(s) should it enable the mode? It provides the build_url_regexp() function for this purpose. Our example looks like so:

build_url_regexp($domain = "github", $allow_www = true);

Notice that we do not include www or the top-level domain as part of the string we give to $domain. This is intentional. By default the function creates a regular expression that assumes com is a valid top-level domain; if we need to allow more we can use the optional $tlds parameter, an array of strings naming accepting top-level domains. The optional $allow_www parameter tells Conkeror whether it should enable the mode for both example.com and http://www.example.com. The default value for $allow_www is false so you will often want to explicitly provide it.

The result of define_keymaps_page_mode() is a variable with the name github_mode which represents the mode itself. You will see that we use as the argument to page_mode_activate() right after its definition. It should be easy to guess what that function does.

The skeleton finally ends with provide("github"), telling Conkeror that this file provides the github module, i.e. what it should load when it sees require("github") elsewhere in our configuration.

Actually Cloning a Repository

Remember the prerequisite shell script previously mentioned? It expects us to give it the URI for the GitHub repository we want to clone. That means we need to come up with a way to extract that URI from GitHub itself. There are a few ways we could do this. My decision was to take advantage of the following fact: A project page on GitHub always has a tag like so:

<meta property="og:url" content="https://github.com/ejmr/php-mode"/>

This is available even on issue pages, pull request pages, and so forth. So the task is to extract the URI from that content attribute and then send it to our shell script. I will begin with the code for the complete command and then provide an explanation of each step.

interactive(
  "github-clone-current-buffer-url", null,
  function (I) {
    var meta = I.buffer.document.querySelector("meta[property='og:url']");
    if (meta) {
      var uri = meta.getAttribute("content").toString();
      yield shell_command_with_argument("/home/eric/Scripts/git-clone-from-uri {}", uri);
      I.minibuffer.message("Cloned " + uri);
    }
    else {
      I.minibuffer.message("Cannot find URL to clone");
    }
  }
);

We use interactive() to create new commands. The first argument is the name of the command as a string, which becomes acceptable to M-x, i.e. we could run M-x github-clone-current-buffer-url. The second argument is a string of documentation, which I omit here—not a great practice on my part. The third and final required argument is the function which implements the command. That function accepts one argument: the interactive context.

The Conkeror wiki speaks some about the interactive context. However, my personal suggestion is to read the Conkeror source code for examples. Most of the JavaScript that powers the browser resides in the modules sub-directory of its source code repository. Searching for the code function (I) will present a plethora of examples of what you can do with the interactive context object, always named I in Conkeror by convention.

In the code above we see that the interactive context provides us with access to the DOM document object for the site in the current buffer (i.e. the GitHub page). We use it to call querySelector() with a CSS selector that will return the element object for that <meta/> tag.

Before going any farther let’s note that we test whether or not our meta variable contains anything at all. If we cannot find the related tag then we print a relevant error message in the mini-buffer. That is another example of one of the countless things we have access to via the interactive context, and you will see page modes often using I to display messages in the mini-buffer.

If we do find that <meta/> tag then we can get the value of its content attribute in a straight-forward way. With that all we need to do is call our shell script from the beginning of the article. Conkeror provides multiple ways to invoke external programs but our code uses shell_command_with_argument(). That function lets us call a shell command using the string {} anywhere we want to insert the shell argument, which ought to be familiar to anyone who uses find, xargs, and some other command shell programs.

Note well that we use yield when running the shell script. Conkeror yields values from some functions instead of using a traditional return to make it asynchronous code easier to write. If you are unfamiliar with yield in JavaScript then I recommend the guide at MDN as a good introduction.

Conclusion

Hopefully you can now understand the complete code for the page mode. The only thing I did not mention was my use of define_key(github_keymap, "C", "github-clone-current-buffer-url") so that I can clone GitHub repositories by pressing that single key. If you have any questions about the code for that page mode, or about Conkeror scripting in general, then please feel free to post the questions as comments on this article.

Advertisements

2 thoughts on “Cloning From GitHub With One Key in Conkeror

    1. Sorry about that. I recently went through and deleted a bunch of old gists and forgot this article linked to one of them. The code that it linked to is below:

      /**
       * This defines my own mode for GitHub to help me quickly clone
       * repositories that interest me.
       */
      
      require("content-buffer.js");
      
      define_keymap("github_keymap", $display_name = "github");
      
      interactive(
        "github-clone-current-buffer-url", null,
        function (I) {
          var meta = I.buffer.document.querySelector("meta[property='og:url']");
          if (meta) {
            var uri = meta.getAttribute("content").toString();
            yield shell_command_with_argument("/home/eric/Scripts/git-clone-from-uri {}", uri);
            I.minibuffer.message("Cloned " + uri);
          }
          else {
            I.minibuffer.message("Cannot find URL to clone");
          }
        }
      );
      
      define_key(github_keymap, "C", "github-clone-current-buffer-url");
      
      define_keymaps_page_mode("github-mode",
          build_url_regexp($domain = "github", $allow_www = true),
          { normal: github_keymap },
          $display_name = "GitHub");
      
      page_mode_activate(github_mode);
      
      provide("github");
      

Add Your Thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s