You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
<p>You will be limited to 60 requests/hour to the API (where each console command may use multipl requests). If you
75
+
<p>You will be limited to 60 requests/hour to the API (where each console command may use multiple requests). If you
76
76
<ahref='http://josh.claassen.net/github'>authenticate via GitHub</a>, you will have a more flexible 5000 requests/hour to play with.
77
77
</p>
78
78
79
+
<h2>Annotated Source</h2>
80
+
81
+
<p>The purpose of this tutorial is less about the specific code, but rather how
82
+
<code>Josh.Shell</code> is wired up to a remote REST API via asynchronous calls to procude an interactive command line interface. Rather than walking through the code line by line, we will explain the flow of the GitHub console and the functions that make it possible, leaving the implementation details to the
83
+
<ahref="docs/githubconsole.html">annotated source code</a>. Throughout the tutorial, functions are linked to their location in the annotated source code.
84
+
</p>
85
+
79
86
<h2>Application State</h2>
80
87
81
88
<p>In order for file system commands to always be available, the console must always have a current repository, which in turn means we'll always have an active user. That means that at any point in time, we will have the current user object, the list of all the user's repositories, and the root of the current branch as state. Changing users, picks a default repository and branch, so that we are never in state without this full state. Branches and current directory information are loaded on demand.</p>
<p>In order for us to show the console, we have to have initialized a user, a repository and retrieved it's root directory. Because of authentication, we have two initialization paths,
131
+
<strong>authenticated</strong> and <strong>unauthenticated</strong>. Both happen via
<em>(Sidenote: This is called after document.ready by an ajax response that tries to fetch the access token from our OAuth helper application).</em>
134
+
</p>
135
+
136
+
<p>Depending on the existence of an <em>access_token</em>
137
+
<ahref="docs/githubconsole.html#initialize" target="_blank"><code>initialize(access_token)</code></a> will either fetch the current user and initialize a default repo or call
138
+
<code>setUser</code> for a default user.</p>
139
+
140
+
<p>The authenticated initialization looks like this:</p>
141
+
<pre>
142
+
get("user", null, function(user) {
143
+
if(!user) {
144
+
return initializationError("user", "unable able to fetch default user");
145
+
}
146
+
_console.log("intializing w/ user '" + user.login + "'");
147
+
return initializeRepos(user, null,
148
+
function(msg) {
149
+
initializationError("repo init", msg);
150
+
},
151
+
function(repo) {
152
+
_self.user = user;
153
+
initializeUI();
154
+
}
155
+
);
156
+
});</pre>
157
+
<p>All API access goes through a helper function
158
+
<ahref="docs/githubconsole.html#get" target="_blank"><code>get(resource, args, callback)</code></a>, which is responsible constructing the
159
+
<em>jsonp</em> call and inspecting the response. For simplicity, all error conditions just result in
160
+
<em>callback</em> being called with null argument instead of a
161
+
<em>json</em> payload. Also for simplicity, any initialization failure, just bails out via
<ahref="docs/githubconsole.html#initializeRepos" target="_blank"><code>initializeRepos(user, repo_name, err, callback)</code></a>, with a null repo_name to fetch all repos for the user and pick the first one as the default one, before setting the authenticated user as the current user and initializing the UI of the shell.
166
+
</p>
167
+
168
+
<p>The unauthenticated flow simply calls
169
+
<ahref="docs/githubconsole.html#setUser" target="_blank"><code>setUser(user_name, repo_name, err, callback)</code></a>, which is the same function we will use to switch users later. This function follows the pattern of providing both an
170
+
<em>error</em> and
171
+
<em>success</em> callback, since once the shell is initialized we need to make sure that any action we take on its behalf does result in its callback being called with some value, lest the shell stop functioning. Unlike previous tutorials, we're now doing network requests and those will fail sooner or later. For this reason we need to make sure we always have a quick
172
+
<em>err</em> callback to stop the current operation and call the callback provided by
173
+
<code>Josh.Shell</code> on command execution. We also need to make sure that we do not mutate the application state until we are done with all operations that can fail, so that worst case is us reporting to the shell that the operation failed and our current, known good state is preserved.
174
+
</p>
175
+
176
+
<h2>Adding Commands</h2>
177
+
178
+
<p>Commands are added via <code>Josh.Shell.SetCommandHandler(cmd,handler)</code> where
179
+
<em>handler</em> is an object with two properties, <em>exec</em> and
180
+
<em>completion</em>. The signature of the execution handler is
181
+
<code>function(cmd, args, callback)</code>. Unlike the callback pattern we used for
182
+
<code>setUser</code>, Josh functions never have an error handler. Since Josh interacts with the UI, it has no concept of failure -- it has to continue executing. It is up to the caller to deal with errors and transform them into the appropriate UI response. But it still gives us the flexibility to undertake an asynchronous actions, such as calling a remote API and complete the function execution upon the asynchronous return of the remote call.
<p>The <ahref="docs/githubconsole.html#cmd.user" target="_blank"><code>user</code></a> command does not have
188
+
<code>TAB</code> completion, since doing efficient tab completion against the full set of GitHub users is beyond this tutorial. Instead it expects a valid username to call
189
+
<ahref="docs/githubconsole.html#setUser" target="_blank"><code>setUser(user_name, repo_name, err, callback)</code></a> with and on completion renders the user template with the new current user.
190
+
</p>
121
191
192
+
<p>If called without a <em>username</em>, we simply render user template with the current user.</p>
<ahref="docs/githubconsole.html#cmd.repo" target="_blank"><code>repo</code></a> command can either show information about the current repository, change the current repository or list all repositories belonging to the user. It also provides
198
+
<code>TAB</code> completion of partial repository names against the repositories of the current user.</p>
199
+
200
+
<p>Given no argument, we simply render the repository template with the current repository</p>
201
+
202
+
<p>If the argument is
203
+
<em>-l</em>, we render the repository list template with the repositories we fetched on user initialization.</p>
204
+
205
+
<p>Finally, the argument is used to try and retrieve the repository from the known repositories. If that succeeds, we call
206
+
<ahref="docs/githubconsole.html#setRepo" target="_blank"><code>setRepo(repo, err, callback)</code></a>, which fetches the root directory to initialize the current node of
207
+
<code>Josh.PathHandler</code> before changing the current repository to the one specified. Upon switching we again render the repository template with the now current repository.
208
+
</p>
209
+
210
+
<p>The completion handler for the command simply calls
211
+
<code>Josh.Shell.bestMatch</code> with the partial argument and a list of all repository names.
212
+
<code>bestMatch</code> takes care of creating the completion object with the appropriate argument completion and list of possible choices.
<p>T can either show information about the current repository, change the current repository or list all repositories belonging to the user. It also provides
219
+
<code>TAB</code> completion of partial repository names against the repositories of the current user.</p>
220
+
221
+
<p>Given no argument, the
222
+
<ahref="docs/githubconsole.html#cmd.branch" target="_blank"><code>branch</code></a> command simply prints the current branch name. The
223
+
<em>-l</em> argument renders a list of all known branches for the current repository, while an actualy branchname as argument will cause the console to change its current branch.</p>
224
+
225
+
<p>Showing the list of branches uses <ahref="docs/githubconsole.html#ensureBranches" target="_blank"><code>ensureBranches(err, callback)</code></a> to lazily initialize the list of branches from the API.</p>
226
+
227
+
<p>The completion handler for the command simply calls
228
+
<code>Josh.Shell.bestMatch</code> with the partial argument and a list of all repository names.
229
+
<code>bestMatch</code> takes care of creating the completion object with the appropriate argument completion and list of possible choices.
0 commit comments