GitHub revolutionized software development by responding to a deep desire to share information. But calling it just "sharing" does a disservice to the tools GitHub provides: these tools remove barriers to communication and streamline workflows. These tools also arose at exactly the moment when the information technology revolution forced companies to adopt more open technologies that assisted an emerging remote workforce.
Gists service part of this need: they permit intimate code sharing and reuse, refactoring, and experimentation in a way not served by the heavyweight tools predating it. In this chapter we will explore using gists to share code, and then build an application hosted as a gist that uses the Gist API.
Gists are straightforward to create. You copy a snippet of code into the large text box in the center, optionally enter in a description or filename, and then choose between a public or private gist. Once your gist has been created you are presented with a URL to share. Gists autodetect the language in most cases and syntax highlight according to the language when displayed as in Documenting JSON using a gist.
There are other services that do this: pastebin was the first, and there are many others that offer variances on code sharing. But gists by GitHub are not simply a pasting service. Gists are first-class repositories, forkable, editable, and expansive. We’ll go over the basics of what gists are, and how to create them, and then show how they allow you to share code that is also a live application.
Every gist
created is a tiny repository. You can update gists and see
the history using git log
. You can download gists, hack on the
repository, and git push
them back into the repository on
gist.github.com (which will republish them onto the publicly
facing web page). And, you can "fork" gists, just like
any other repository.
You are allowed to branch within gist repositories; however, branches are not displayed inside of gist.github.com/. But if you need the benefits of branching when using gists you can branch normally inside a repository and the branch information is retained on the upstream repository after you push it up.
You can have an unlimited number of public and secret gists. Secret gists can, in many cases, replace private repositories, and these secret gists don’t count against the limited amount of private repositories you have with paid GitHub accounts. Or, you can make a gist public, and share that URL to mailing lists or anywhere you need public feedback.
Note
|
As there are two types of gists (public and secret), it is important to understand the differences between them. Public gists are searchable. Secret gists are not searchable, but they are accessible to anyone who knows the URL. Don’t post any code to a gist you need to keep secret as once you put it there, it is only as safe as the URL is secret. |
Most people share gists through the URL, but you can embed gists inside of other contexts (like blogs) and get a simple and pretty snippet of code.
To embed inside of an HTML page look for the "Embed this gist" box to
the left of a gist. Copy the code listed there (which will look
something like <script src="https://gist.github.com/xrd/8923697.js"></script>
) and paste it
into your HTML.
If you wish to include only a particular file from the gist (if it
contains multiple files), then add ?file=hi.rb
to the end of the
URL specified in the src attribute.
Jekyll blogs (explained in [Jekyll]) can easily host
gists using a special syntax. The shortcut {% gist 8138797 %}
will
embed a private gist, which would be found at
http://gist.github.com/8138797. If you want to use a
specific file within the gist, add a filename to the gist code like
{% gist 8138797 hi.rb %}
. Secret gists can also be embedded. If you
use a secret gist, prefix the username of the account holder in the
gist like so: {% gist xrd/8138797 hi.rb %}
.
Now let’s look at creating gists from outside the GitHub.com site, using the command-line.
gem install gist
will install a command line tool that helps create gists. You can use it simply by typing the command, and then
entering the data you want to post as a gist:
$ gist
(type a gist. <ctrl-c> to cancel, <ctrl-d> when done)
{ "foo" : "bar" }
https://gist.github.com/9106765
The gist command will return the link to the gist just created. Gists
are created anonymously by default. You can log in using the --login
switch. Once you do this, your gists will be linked to
your account:
$ gist --login
Obtaining OAuth2 access_token from github.
GitHub username: xrd
GitHub password:
2-factor auth code: 787878
Success! https://github.com/settings/applications
You can pipe text to the gist command to use the contents of that file:
$ echo '{ "foo" : "bar" }' | gist
https://gist.github.com/9106799
You can also cat
a file to gist:
$ cat MyJavaFile.java | gist
https://gist.github.com/9345609
Gists are often used to show interesting or troublesome code, and there
are times when you don’t want to display the entirety of a file. In
this case the command-line grep
tool can be useful; grep
searches
for a specific piece of code and with the right switches can include
several lines of context around that code inside a gist. This command
looks for the function myFunction
inside the MyJavaFile.java file
and then prints the next 20 lines of context and stores it as a gist:
$ grep -A 20 myFunction MyJavaFile.java | gist
https://gist.github.com/9453069
Adding the -o
switch automatically opens the gist inside your
default web browser. You can also copy the gist URL to the clipboard
using the -c
switch. Or, you can copy the contents of your clipboard
into a gist using the -P
switch.
There are many other fun features of the gist command. To learn more
run the gist command with the --help
switch.
As gists are themselves repositories, you can use them for dual purposes: for hosting code samples, and for code samples that are themselves fully working and packaged applications inside a gist repository.
Let’s build a simple Sinatra application to showcase how code hosted as a gist can also be a living application. Sinatra is a Ruby library for creating dead-simple web servers. A Sinatra program can be as simple as this:
require 'sinatra'
get '/hi' do
"Hello World!"
end
Create a gist for this by visiting gist.github.com. Enter in the text exactly as shown and then choose public gist.
You now have a share-friendly gist of code anyone can use to review. More importantly, this is a repository with executable code. To clone it, look for the Clone URL to the right of the gist itself. You will likely see a Git protocol URL and an HTTPS URL. If you are cloning the URL and intend only to read the gist, you can use the HTTPS URL. You technically can push changes once you have cloned a repository using the HTTPS URL but not if you have two-factor authentication enabled. In most cases it is easier and more flexible to use the Git protocol URL.
Let’s clone it now:
$ git clone [email protected]:8138797.git
Once you have cloned the repository, go inside it. You’ll see a list of files, a list that right now includes only one file:
$ cd 8138797
$ ls
hi.rb
This code is exectuable: to run it enter ruby hi.rb
.
If you have not used Sinatra with Ruby before, this will cause an error. This program requires a library called "sinatra" and you have not yet installed it. We could write a read me file, or add documentation into this file itself. Another way to guarantee the user has the proper files installed is to use a Gemfile, which is a file that tells which libraries are installed and from where. That sounds like the best way:
$ printf "source 'https://rubygems.org'\ngem 'sinatra'" > Gemfile
The bundle
command (from the bundler gem) will install Sinatra and
the associated dependencies:
$ bundle
Using rack (1.5.2)
Using rack-protection (1.5.1)
Using tilt (1.4.1)
Using sinatra (1.4.4)
Using bundler (1.3.5)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.
Why did we do things this way? Because now we can add the Gemfile to our repository locally, and then publish into our gist for sharing on the Web. Our repository now not only has the code, but a well-known manifest file that explains the necessary components when running the code.
Let’s add to our application and use the Octokit Ruby gem to pull all public gists for any user we specify. The Octokit library is the the official Ruby library for accessing the GitHub API. Why would we want to make a gist that displays other gists? Self-referential meta code is all the rage, the modern-day response to René Magritte’s famous work: "Ceci n’est pas une pipe."[1]
Add a view index.erb at the root of our directory:
<html>
<body>
User has <%= count %> public gists
</body>
</html>
Add the Octokit gem to our Gemfile:
gem "octokit"
Run bundle
to install Octokit. Then, modify our hi.rb app to look
like this:
require 'sinatra'
require 'octokit'
set :views, "."
get '/:username' do |username|
user = Octokit.user username
count = user.public_gists
erb :index, locals: { :count => count }
end
Our filesystem should look like this, with three files:
$ ls -1
Gemfile
hi.rb
index.erb
Restart Sinatra by running Ctrl-C and then ruby hi.rb
. If you visit http://localhost:4567/xrd in
your browser, you will see the count of public gists for user xrd
(Displaying the gist count);
modify the username in the URL to specify any GitHub username and you will see
their last five gists displayed.
The GitHub API uses hypermedia instead of basic resource-driven APIs. If you use a client like Octokit, the hypermedia details are hidden behind an elegant Ruby client. But there is a benefit to understanding how hypermedia works when you need to retrieve deeper information from the GitHub API.
Most RESTful APIs come with a "sitemap," generally an API reference document that tells a user which endpoints to use. You view the resources available from that API and then apply some HTTP verb to do something to them. Hypermedia thinks of an API differently. Hypermedia APIs describe themselves inside their responses using "affordances." What this means is that the API might respond like this:
{
"_links": {
"self": {
"href": "http://shop.oreilly.com/product/0636920030300.do"
}
}
"id": "xrd",
"name": "Chris Dawson"
}
In this payload, you can see that there is an id ("xrd") and a name ("Chris Dawson"). This particular payload was forked from the HAL explanation at the HAL Primer document, and you can find a more detailed explanation of these concepts there.
The important thing to note about hypermedia APIs is that payloads contain metadata about data itself and metadata about the possible options of operating on the data. RESTful APIs typically provide a mapping outside of the payload. You have to join the API sitemap with the data in an ad hoc way when using RESTful APIs; with hypermedia APIs your client can react to the payload itself correctly and intelligently without knowing anything about a sitemap stored in human-readable documentation.
This loose coupling makes APIs and their clients flexible. In theory, a hypermedia API works intuitively with a hypermedia-aware client. If you change the API, the client, as it understands hypermedia, can react and still work as expected. Using a RESTful API means that clients must be updated (a newer version of the client must be installed) or the client code must be upgraded. Hypermedia APIs can alter their backend, and then the client, as long as it is hypermedia-aware, can automatically and dynamically determine the right way to access information from the response itself. In other words, with a hypermedia client the API backend can change and your client code should not need to.
This is explained in great detail in the book Building Hypermedia APIs with HTML5 and Node (O'Reilly).
Now that you know a little about hypermedia, let’s navigate it using Octokit:
-
Start at a resource, with code like
user = Octokit.user "xrd"
. This begins the initialization of the client. -
user
now is an object filled with the actual data of the resource. In this case, you could call a method likeuser.followers
to see a meager follower count. -
user
also has hypermedia references. You can see these by callinguser.rels
. This retrieves the relationships described in the hypermedia links. -
Relationships (found by calling
user.rels
) include avatar, self, followers, etc. -
Use a relationship by calling the
get.data
method to retrieve and access the data from the GitHub API (followers = user.rels[:followers].get.data
). -
Calling
.get.data
populates an array of the followers (paged if it exceeds 100 items).
Let’s extend our Sinatra app to retrieve actual data about the user’s gists by using hypermedia references:
require 'sinatra'
require 'octokit'
set :views, "."
helpers do
def h(text)
Rack::Utils.escape_html(text)
end
end
get '/:username' do |username|
gists = Octokit.gists username, :per_page => 5
erb :index, locals: { :gists => gists, username: username }
end
The index.erb file contains code to iterate over each gist and pull
the content. You can see that our response object is an array of
gists, and each has an attribute called fields
. This fields
attribute
specifies the filenames available in each gist. If you reference that
filename against the files, the response includes a hypermedia ref
attribute. Retrieve the raw
content using the Octokit method .get.data
:
<html>
<body>
<h2>User <%= username %>'s last five gists</h2>
<% gists.each do |g| %>
<% g[:files].fields.each do |f| %>
<b><%= f %></b>:
<%= h g[:files][f.to_sym].rels[:raw].get.data %>
<br/>
<br/>
<% end %>
<% end %>
</body>
</html>
Now we see the gists and the contents, as in Last five gists, with details.
In this chapter we looked at gists and learned how they can be used to share code snippets. We built a simple application and stored it as a gist. This application retrieves data from the GitHub API using our first higher-level language client library (the Octokit library for Ruby). We also went deeper into how hypermedia works and how a client library implements using hypermedia metadata.
In the next chapter we will look at Gollum, the GitHub wiki. This chapter provides an introduction to the Rugged Ruby library for accessing Git repositories and the Ruby library for accessing GitHub.