Codebase list golang-github-mreiferson-go-snappystream / 871335c
New upstream version 0.2.3 Dawid Dziurla 4 years ago
14 changed file(s) with 5144 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 language: go
1 go:
2 - 1.2.2
3 - 1.3.1
4 env:
5 - GOARCH=amd64
6 - GOARCH=386
7 install:
8 - go get code.google.com/p/snappy-go/snappy
9 script:
10 - go test -v
11 notifications:
12 email: false
0 Permission is hereby granted, free of charge, to any person obtaining a copy
1 of this software and associated documentation files (the "Software"), to deal
2 in the Software without restriction, including without limitation the rights
3 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
4 copies of the Software, and to permit persons to whom the Software is
5 furnished to do so, subject to the following conditions:
6
7 The above copyright notice and this permission notice shall be included in
8 all copies or substantial portions of the Software.
9
10 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
16 THE SOFTWARE.
0 ## go-snappystream
1
2 a Go package for framed snappy streams.
3
4 [![Build Status](https://secure.travis-ci.org/mreiferson/go-snappystream.png?branch=master)](http://travis-ci.org/mreiferson/go-snappystream) [![GoDoc](https://godoc.org/github.com/mreiferson/go-snappystream?status.svg)](https://godoc.org/github.com/mreiferson/go-snappystream)
5
6 This package wraps [snappy-go][1] and supplies a `Reader` and `Writer`
7 for the snappy [framed stream format][2].
8
9 [1]: https://code.google.com/p/snappy-go/
10 [2]: https://snappy.googlecode.com/svn/trunk/framing_format.txt
0 package snappystream
1
2 var testDataMan = []byte(`
3 .TH XARGS 1L \" -*- nroff -*-
4 .SH NAME
5 xargs \- build and execute command lines from standard input
6 .SH SYNOPSIS
7 .B xargs
8 [\-0prtx] [\-e[eof-str]] [\-i[replace-str]] [\-l[max-lines]]
9 [\-n max-args] [\-s max-chars] [\-P max-procs] [\-\-null] [\-\-eof[=eof-str]]
10 [\-\-replace[=replace-str]] [\-\-max-lines[=max-lines]] [\-\-interactive]
11 [\-\-max-chars=max-chars] [\-\-verbose] [\-\-exit] [\-\-max-procs=max-procs]
12 [\-\-max-args=max-args] [\-\-no-run-if-empty] [\-\-version] [\-\-help]
13 [command [initial-arguments]]
14 .SH DESCRIPTION
15 This manual page
16 documents the GNU version of
17 .BR xargs .
18 .B xargs
19 reads arguments from the standard input, delimited by blanks (which can be
20 protected with double or single quotes or a backslash) or newlines,
21 and executes the
22 .I command
23 (default is /bin/echo) one or more times with any
24 .I initial-arguments
25 followed by arguments read from standard input. Blank lines on the
26 standard input are ignored.
27 .P
28 .B xargs
29 exits with the following status:
30 .nf
31 0 if it succeeds
32 123 if any invocation of the command exited with status 1-125
33 124 if the command exited with status 255
34 125 if the command is killed by a signal
35 126 if the command cannot be run
36 127 if the command is not found
37 1 if some other error occurred.
38 .fi
39 .SS OPTIONS
40 .TP
41 .I "\-\-null, \-0"
42 Input filenames are terminated by a null character instead of by
43 whitespace, and the quotes and backslash are not special (every
44 character is taken literally). Disables the end of file string, which
45 is treated like any other argument. Useful when arguments might
46 contain white space, quote marks, or backslashes. The GNU find
47 \-print0 option produces input suitable for this mode.
48 .TP
49 .I "\-\-eof[=eof-str], \-e[eof-str]"
50 Set the end of file string to \fIeof-str\fR. If the end of file
51 string occurs as a line of input, the rest of the input is ignored.
52 If \fIeof-str\fR is omitted, there is no end of file string. If this
53 option is not given, the end of file string defaults to "_".
54 .TP
55 .I "\-\-help"
56 Print a summary of the options to
57 .B xargs
58 and exit.
59 .TP
60 .I "\-\-replace[=replace-str], \-i[replace-str]"
61 Replace occurences of \fIreplace-str\fR in the initial arguments with
62 names read from standard input.
63 Also, unquoted blanks do not terminate arguments.
64 If \fIreplace-str\fR is omitted, it
65 defaults to "{}" (like for 'find \-exec'). Implies \fI\-x\fP and
66 \fI\-l 1\fP.
67 .TP
68 .I "\-\-max-lines[=max-lines], -l[max-lines]"
69 Use at most \fImax-lines\fR nonblank input lines per command line;
70 \fImax-lines\fR defaults to 1 if omitted. Trailing blanks cause an
71 input line to be logically continued on the next input line. Implies
72 \fI\-x\fR.
73 .TP
74 .I "\-\-max-args=max-args, \-n max-args"
75 Use at most \fImax-args\fR arguments per command line. Fewer than
76 \fImax-args\fR arguments will be used if the size (see the \-s option)
77 is exceeded, unless the \-x option is given, in which case \fBxargs\fR
78 will exit.
79 .TP
80 .I "\-\-interactive, \-p"
81 Prompt the user about whether to run each command line and read a line
82 from the terminal. Only run the command line if the response starts
83 with 'y' or 'Y'. Implies \fI\-t\fR.
84 .TP
85 .I "\-\-no-run-if-empty, \-r"
86 If the standard input does not contain any nonblanks, do not run the
87 command. Normally, the command is run once even if there is no input.
88 .TP
89 .I "\-\-max-chars=max-chars, \-s max-chars"
90 Use at most \fImax-chars\fR characters per command line, including the
91 command and initial arguments and the terminating nulls at the ends of
92 the argument strings. The default is as large as possible, up to 20k
93 characters.
94 .TP
95 .I "\-\-verbose, \-t"
96 Print the command line on the standard error output before executing
97 it.
98 .TP
99 .I "\-\-version"
100 Print the version number of
101 .B xargs
102 and exit.
103 .TP
104 .I "\-\-exit, \-x"
105 Exit if the size (see the \fI\-s\fR option) is exceeded.
106 .TP
107 .I "\-\-max-procs=max-procs, \-P max-procs"
108 Run up to \fImax-procs\fR processes at a time; the default is 1. If
109 \fImax-procs\fR is 0, \fBxargs\fR will run as many processes as
110 possible at a time. Use the \fI\-n\fR option with \fI\-P\fR;
111 otherwise chances are that only one exec will be done.
112 .SH "SEE ALSO"
113 \fBfind\fP(1L), \fBlocate\fP(1L), \fBlocatedb\fP(5L), \fBupdatedb\fP(1)
114 \fBFinding Files\fP (on-line in Info, or printed)`)
115
116 // curl -s https://api.github.com/users/mreiferson/repos
117 var testDataJSON = []byte(`
118 [
119 {
120 "id": 19041094,
121 "name": "2014-talks",
122 "full_name": "mreiferson/2014-talks",
123 "owner": {
124 "login": "mreiferson",
125 "id": 187441,
126 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
127 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
128 "url": "https://api.github.com/users/mreiferson",
129 "html_url": "https://github.com/mreiferson",
130 "followers_url": "https://api.github.com/users/mreiferson/followers",
131 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
132 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
133 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
134 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
135 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
136 "repos_url": "https://api.github.com/users/mreiferson/repos",
137 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
138 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
139 "type": "User",
140 "site_admin": false
141 },
142 "private": false,
143 "html_url": "https://github.com/mreiferson/2014-talks",
144 "description": "This is the official repository for slides and talks from GopherCon 2014",
145 "fork": true,
146 "url": "https://api.github.com/repos/mreiferson/2014-talks",
147 "forks_url": "https://api.github.com/repos/mreiferson/2014-talks/forks",
148 "keys_url": "https://api.github.com/repos/mreiferson/2014-talks/keys{/key_id}",
149 "collaborators_url": "https://api.github.com/repos/mreiferson/2014-talks/collaborators{/collaborator}",
150 "teams_url": "https://api.github.com/repos/mreiferson/2014-talks/teams",
151 "hooks_url": "https://api.github.com/repos/mreiferson/2014-talks/hooks",
152 "issue_events_url": "https://api.github.com/repos/mreiferson/2014-talks/issues/events{/number}",
153 "events_url": "https://api.github.com/repos/mreiferson/2014-talks/events",
154 "assignees_url": "https://api.github.com/repos/mreiferson/2014-talks/assignees{/user}",
155 "branches_url": "https://api.github.com/repos/mreiferson/2014-talks/branches{/branch}",
156 "tags_url": "https://api.github.com/repos/mreiferson/2014-talks/tags",
157 "blobs_url": "https://api.github.com/repos/mreiferson/2014-talks/git/blobs{/sha}",
158 "git_tags_url": "https://api.github.com/repos/mreiferson/2014-talks/git/tags{/sha}",
159 "git_refs_url": "https://api.github.com/repos/mreiferson/2014-talks/git/refs{/sha}",
160 "trees_url": "https://api.github.com/repos/mreiferson/2014-talks/git/trees{/sha}",
161 "statuses_url": "https://api.github.com/repos/mreiferson/2014-talks/statuses/{sha}",
162 "languages_url": "https://api.github.com/repos/mreiferson/2014-talks/languages",
163 "stargazers_url": "https://api.github.com/repos/mreiferson/2014-talks/stargazers",
164 "contributors_url": "https://api.github.com/repos/mreiferson/2014-talks/contributors",
165 "subscribers_url": "https://api.github.com/repos/mreiferson/2014-talks/subscribers",
166 "subscription_url": "https://api.github.com/repos/mreiferson/2014-talks/subscription",
167 "commits_url": "https://api.github.com/repos/mreiferson/2014-talks/commits{/sha}",
168 "git_commits_url": "https://api.github.com/repos/mreiferson/2014-talks/git/commits{/sha}",
169 "comments_url": "https://api.github.com/repos/mreiferson/2014-talks/comments{/number}",
170 "issue_comment_url": "https://api.github.com/repos/mreiferson/2014-talks/issues/comments/{number}",
171 "contents_url": "https://api.github.com/repos/mreiferson/2014-talks/contents/{+path}",
172 "compare_url": "https://api.github.com/repos/mreiferson/2014-talks/compare/{base}...{head}",
173 "merges_url": "https://api.github.com/repos/mreiferson/2014-talks/merges",
174 "archive_url": "https://api.github.com/repos/mreiferson/2014-talks/{archive_format}{/ref}",
175 "downloads_url": "https://api.github.com/repos/mreiferson/2014-talks/downloads",
176 "issues_url": "https://api.github.com/repos/mreiferson/2014-talks/issues{/number}",
177 "pulls_url": "https://api.github.com/repos/mreiferson/2014-talks/pulls{/number}",
178 "milestones_url": "https://api.github.com/repos/mreiferson/2014-talks/milestones{/number}",
179 "notifications_url": "https://api.github.com/repos/mreiferson/2014-talks/notifications{?since,all,participating}",
180 "labels_url": "https://api.github.com/repos/mreiferson/2014-talks/labels{/name}",
181 "releases_url": "https://api.github.com/repos/mreiferson/2014-talks/releases{/id}",
182 "created_at": "2014-04-22T18:28:59Z",
183 "updated_at": "2014-04-26T03:10:39Z",
184 "pushed_at": "2014-04-25T14:46:35Z",
185 "git_url": "git://github.com/mreiferson/2014-talks.git",
186 "ssh_url": "git@github.com:mreiferson/2014-talks.git",
187 "clone_url": "https://github.com/mreiferson/2014-talks.git",
188 "svn_url": "https://github.com/mreiferson/2014-talks",
189 "homepage": null,
190 "size": 3596,
191 "stargazers_count": 0,
192 "watchers_count": 0,
193 "language": null,
194 "has_issues": false,
195 "has_downloads": true,
196 "has_wiki": true,
197 "forks_count": 0,
198 "mirror_url": null,
199 "open_issues_count": 0,
200 "forks": 0,
201 "open_issues": 0,
202 "watchers": 0,
203 "default_branch": "master"
204 },
205 {
206 "id": 3329246,
207 "name": "asyncdynamo",
208 "full_name": "mreiferson/asyncdynamo",
209 "owner": {
210 "login": "mreiferson",
211 "id": 187441,
212 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
213 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
214 "url": "https://api.github.com/users/mreiferson",
215 "html_url": "https://github.com/mreiferson",
216 "followers_url": "https://api.github.com/users/mreiferson/followers",
217 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
218 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
219 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
220 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
221 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
222 "repos_url": "https://api.github.com/users/mreiferson/repos",
223 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
224 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
225 "type": "User",
226 "site_admin": false
227 },
228 "private": false,
229 "html_url": "https://github.com/mreiferson/asyncdynamo",
230 "description": "async Amazon DynamoDB library for Tornado",
231 "fork": true,
232 "url": "https://api.github.com/repos/mreiferson/asyncdynamo",
233 "forks_url": "https://api.github.com/repos/mreiferson/asyncdynamo/forks",
234 "keys_url": "https://api.github.com/repos/mreiferson/asyncdynamo/keys{/key_id}",
235 "collaborators_url": "https://api.github.com/repos/mreiferson/asyncdynamo/collaborators{/collaborator}",
236 "teams_url": "https://api.github.com/repos/mreiferson/asyncdynamo/teams",
237 "hooks_url": "https://api.github.com/repos/mreiferson/asyncdynamo/hooks",
238 "issue_events_url": "https://api.github.com/repos/mreiferson/asyncdynamo/issues/events{/number}",
239 "events_url": "https://api.github.com/repos/mreiferson/asyncdynamo/events",
240 "assignees_url": "https://api.github.com/repos/mreiferson/asyncdynamo/assignees{/user}",
241 "branches_url": "https://api.github.com/repos/mreiferson/asyncdynamo/branches{/branch}",
242 "tags_url": "https://api.github.com/repos/mreiferson/asyncdynamo/tags",
243 "blobs_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/blobs{/sha}",
244 "git_tags_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/tags{/sha}",
245 "git_refs_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/refs{/sha}",
246 "trees_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/trees{/sha}",
247 "statuses_url": "https://api.github.com/repos/mreiferson/asyncdynamo/statuses/{sha}",
248 "languages_url": "https://api.github.com/repos/mreiferson/asyncdynamo/languages",
249 "stargazers_url": "https://api.github.com/repos/mreiferson/asyncdynamo/stargazers",
250 "contributors_url": "https://api.github.com/repos/mreiferson/asyncdynamo/contributors",
251 "subscribers_url": "https://api.github.com/repos/mreiferson/asyncdynamo/subscribers",
252 "subscription_url": "https://api.github.com/repos/mreiferson/asyncdynamo/subscription",
253 "commits_url": "https://api.github.com/repos/mreiferson/asyncdynamo/commits{/sha}",
254 "git_commits_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/commits{/sha}",
255 "comments_url": "https://api.github.com/repos/mreiferson/asyncdynamo/comments{/number}",
256 "issue_comment_url": "https://api.github.com/repos/mreiferson/asyncdynamo/issues/comments/{number}",
257 "contents_url": "https://api.github.com/repos/mreiferson/asyncdynamo/contents/{+path}",
258 "compare_url": "https://api.github.com/repos/mreiferson/asyncdynamo/compare/{base}...{head}",
259 "merges_url": "https://api.github.com/repos/mreiferson/asyncdynamo/merges",
260 "archive_url": "https://api.github.com/repos/mreiferson/asyncdynamo/{archive_format}{/ref}",
261 "downloads_url": "https://api.github.com/repos/mreiferson/asyncdynamo/downloads",
262 "issues_url": "https://api.github.com/repos/mreiferson/asyncdynamo/issues{/number}",
263 "pulls_url": "https://api.github.com/repos/mreiferson/asyncdynamo/pulls{/number}",
264 "milestones_url": "https://api.github.com/repos/mreiferson/asyncdynamo/milestones{/number}",
265 "notifications_url": "https://api.github.com/repos/mreiferson/asyncdynamo/notifications{?since,all,participating}",
266 "labels_url": "https://api.github.com/repos/mreiferson/asyncdynamo/labels{/name}",
267 "releases_url": "https://api.github.com/repos/mreiferson/asyncdynamo/releases{/id}",
268 "created_at": "2012-02-01T21:32:54Z",
269 "updated_at": "2014-04-03T21:58:44Z",
270 "pushed_at": "2012-02-01T21:06:23Z",
271 "git_url": "git://github.com/mreiferson/asyncdynamo.git",
272 "ssh_url": "git@github.com:mreiferson/asyncdynamo.git",
273 "clone_url": "https://github.com/mreiferson/asyncdynamo.git",
274 "svn_url": "https://github.com/mreiferson/asyncdynamo",
275 "homepage": "",
276 "size": 73,
277 "stargazers_count": 1,
278 "watchers_count": 1,
279 "language": "Python",
280 "has_issues": false,
281 "has_downloads": true,
282 "has_wiki": true,
283 "forks_count": 0,
284 "mirror_url": null,
285 "open_issues_count": 0,
286 "forks": 0,
287 "open_issues": 0,
288 "watchers": 1,
289 "default_branch": "master"
290 },
291 {
292 "id": 2622445,
293 "name": "asyncmongo",
294 "full_name": "mreiferson/asyncmongo",
295 "owner": {
296 "login": "mreiferson",
297 "id": 187441,
298 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
299 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
300 "url": "https://api.github.com/users/mreiferson",
301 "html_url": "https://github.com/mreiferson",
302 "followers_url": "https://api.github.com/users/mreiferson/followers",
303 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
304 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
305 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
306 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
307 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
308 "repos_url": "https://api.github.com/users/mreiferson/repos",
309 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
310 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
311 "type": "User",
312 "site_admin": false
313 },
314 "private": false,
315 "html_url": "https://github.com/mreiferson/asyncmongo",
316 "description": "An asynchronous library for accessing mongo with tornado.ioloop",
317 "fork": true,
318 "url": "https://api.github.com/repos/mreiferson/asyncmongo",
319 "forks_url": "https://api.github.com/repos/mreiferson/asyncmongo/forks",
320 "keys_url": "https://api.github.com/repos/mreiferson/asyncmongo/keys{/key_id}",
321 "collaborators_url": "https://api.github.com/repos/mreiferson/asyncmongo/collaborators{/collaborator}",
322 "teams_url": "https://api.github.com/repos/mreiferson/asyncmongo/teams",
323 "hooks_url": "https://api.github.com/repos/mreiferson/asyncmongo/hooks",
324 "issue_events_url": "https://api.github.com/repos/mreiferson/asyncmongo/issues/events{/number}",
325 "events_url": "https://api.github.com/repos/mreiferson/asyncmongo/events",
326 "assignees_url": "https://api.github.com/repos/mreiferson/asyncmongo/assignees{/user}",
327 "branches_url": "https://api.github.com/repos/mreiferson/asyncmongo/branches{/branch}",
328 "tags_url": "https://api.github.com/repos/mreiferson/asyncmongo/tags",
329 "blobs_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/blobs{/sha}",
330 "git_tags_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/tags{/sha}",
331 "git_refs_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/refs{/sha}",
332 "trees_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/trees{/sha}",
333 "statuses_url": "https://api.github.com/repos/mreiferson/asyncmongo/statuses/{sha}",
334 "languages_url": "https://api.github.com/repos/mreiferson/asyncmongo/languages",
335 "stargazers_url": "https://api.github.com/repos/mreiferson/asyncmongo/stargazers",
336 "contributors_url": "https://api.github.com/repos/mreiferson/asyncmongo/contributors",
337 "subscribers_url": "https://api.github.com/repos/mreiferson/asyncmongo/subscribers",
338 "subscription_url": "https://api.github.com/repos/mreiferson/asyncmongo/subscription",
339 "commits_url": "https://api.github.com/repos/mreiferson/asyncmongo/commits{/sha}",
340 "git_commits_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/commits{/sha}",
341 "comments_url": "https://api.github.com/repos/mreiferson/asyncmongo/comments{/number}",
342 "issue_comment_url": "https://api.github.com/repos/mreiferson/asyncmongo/issues/comments/{number}",
343 "contents_url": "https://api.github.com/repos/mreiferson/asyncmongo/contents/{+path}",
344 "compare_url": "https://api.github.com/repos/mreiferson/asyncmongo/compare/{base}...{head}",
345 "merges_url": "https://api.github.com/repos/mreiferson/asyncmongo/merges",
346 "archive_url": "https://api.github.com/repos/mreiferson/asyncmongo/{archive_format}{/ref}",
347 "downloads_url": "https://api.github.com/repos/mreiferson/asyncmongo/downloads",
348 "issues_url": "https://api.github.com/repos/mreiferson/asyncmongo/issues{/number}",
349 "pulls_url": "https://api.github.com/repos/mreiferson/asyncmongo/pulls{/number}",
350 "milestones_url": "https://api.github.com/repos/mreiferson/asyncmongo/milestones{/number}",
351 "notifications_url": "https://api.github.com/repos/mreiferson/asyncmongo/notifications{?since,all,participating}",
352 "labels_url": "https://api.github.com/repos/mreiferson/asyncmongo/labels{/name}",
353 "releases_url": "https://api.github.com/repos/mreiferson/asyncmongo/releases{/id}",
354 "created_at": "2011-10-21T19:01:05Z",
355 "updated_at": "2013-01-04T11:58:26Z",
356 "pushed_at": "2011-10-21T19:02:46Z",
357 "git_url": "git://github.com/mreiferson/asyncmongo.git",
358 "ssh_url": "git@github.com:mreiferson/asyncmongo.git",
359 "clone_url": "https://github.com/mreiferson/asyncmongo.git",
360 "svn_url": "https://github.com/mreiferson/asyncmongo",
361 "homepage": "http://github.com/bitly/asyncmongo",
362 "size": 563,
363 "stargazers_count": 1,
364 "watchers_count": 1,
365 "language": "Python",
366 "has_issues": false,
367 "has_downloads": true,
368 "has_wiki": true,
369 "forks_count": 0,
370 "mirror_url": null,
371 "open_issues_count": 0,
372 "forks": 0,
373 "open_issues": 0,
374 "watchers": 1,
375 "default_branch": "master"
376 },
377 {
378 "id": 4554560,
379 "name": "blog.perplexedlabs.com",
380 "full_name": "mreiferson/blog.perplexedlabs.com",
381 "owner": {
382 "login": "mreiferson",
383 "id": 187441,
384 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
385 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
386 "url": "https://api.github.com/users/mreiferson",
387 "html_url": "https://github.com/mreiferson",
388 "followers_url": "https://api.github.com/users/mreiferson/followers",
389 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
390 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
391 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
392 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
393 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
394 "repos_url": "https://api.github.com/users/mreiferson/repos",
395 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
396 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
397 "type": "User",
398 "site_admin": false
399 },
400 "private": false,
401 "html_url": "https://github.com/mreiferson/blog.perplexedlabs.com",
402 "description": "archive of posts",
403 "fork": false,
404 "url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com",
405 "forks_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/forks",
406 "keys_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/keys{/key_id}",
407 "collaborators_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/collaborators{/collaborator}",
408 "teams_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/teams",
409 "hooks_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/hooks",
410 "issue_events_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/issues/events{/number}",
411 "events_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/events",
412 "assignees_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/assignees{/user}",
413 "branches_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/branches{/branch}",
414 "tags_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/tags",
415 "blobs_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/blobs{/sha}",
416 "git_tags_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/tags{/sha}",
417 "git_refs_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/refs{/sha}",
418 "trees_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/trees{/sha}",
419 "statuses_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/statuses/{sha}",
420 "languages_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/languages",
421 "stargazers_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/stargazers",
422 "contributors_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/contributors",
423 "subscribers_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/subscribers",
424 "subscription_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/subscription",
425 "commits_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/commits{/sha}",
426 "git_commits_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/commits{/sha}",
427 "comments_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/comments{/number}",
428 "issue_comment_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/issues/comments/{number}",
429 "contents_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/contents/{+path}",
430 "compare_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/compare/{base}...{head}",
431 "merges_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/merges",
432 "archive_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/{archive_format}{/ref}",
433 "downloads_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/downloads",
434 "issues_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/issues{/number}",
435 "pulls_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/pulls{/number}",
436 "milestones_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/milestones{/number}",
437 "notifications_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/notifications{?since,all,participating}",
438 "labels_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/labels{/name}",
439 "releases_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/releases{/id}",
440 "created_at": "2012-06-05T01:38:40Z",
441 "updated_at": "2014-04-27T23:44:56Z",
442 "pushed_at": "2014-04-27T23:44:56Z",
443 "git_url": "git://github.com/mreiferson/blog.perplexedlabs.com.git",
444 "ssh_url": "git@github.com:mreiferson/blog.perplexedlabs.com.git",
445 "clone_url": "https://github.com/mreiferson/blog.perplexedlabs.com.git",
446 "svn_url": "https://github.com/mreiferson/blog.perplexedlabs.com",
447 "homepage": "http://blog.perplexedlabs.com/",
448 "size": 668,
449 "stargazers_count": 1,
450 "watchers_count": 1,
451 "language": null,
452 "has_issues": true,
453 "has_downloads": true,
454 "has_wiki": true,
455 "forks_count": 0,
456 "mirror_url": null,
457 "open_issues_count": 0,
458 "forks": 0,
459 "open_issues": 0,
460 "watchers": 1,
461 "default_branch": "master"
462 },
463 {
464 "id": 2861903,
465 "name": "btpath",
466 "full_name": "mreiferson/btpath",
467 "owner": {
468 "login": "mreiferson",
469 "id": 187441,
470 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
471 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
472 "url": "https://api.github.com/users/mreiferson",
473 "html_url": "https://github.com/mreiferson",
474 "followers_url": "https://api.github.com/users/mreiferson/followers",
475 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
476 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
477 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
478 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
479 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
480 "repos_url": "https://api.github.com/users/mreiferson/repos",
481 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
482 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
483 "type": "User",
484 "site_admin": false
485 },
486 "private": false,
487 "html_url": "https://github.com/mreiferson/btpath",
488 "description": "A* implementation/test app (1997)",
489 "fork": false,
490 "url": "https://api.github.com/repos/mreiferson/btpath",
491 "forks_url": "https://api.github.com/repos/mreiferson/btpath/forks",
492 "keys_url": "https://api.github.com/repos/mreiferson/btpath/keys{/key_id}",
493 "collaborators_url": "https://api.github.com/repos/mreiferson/btpath/collaborators{/collaborator}",
494 "teams_url": "https://api.github.com/repos/mreiferson/btpath/teams",
495 "hooks_url": "https://api.github.com/repos/mreiferson/btpath/hooks",
496 "issue_events_url": "https://api.github.com/repos/mreiferson/btpath/issues/events{/number}",
497 "events_url": "https://api.github.com/repos/mreiferson/btpath/events",
498 "assignees_url": "https://api.github.com/repos/mreiferson/btpath/assignees{/user}",
499 "branches_url": "https://api.github.com/repos/mreiferson/btpath/branches{/branch}",
500 "tags_url": "https://api.github.com/repos/mreiferson/btpath/tags",
501 "blobs_url": "https://api.github.com/repos/mreiferson/btpath/git/blobs{/sha}",
502 "git_tags_url": "https://api.github.com/repos/mreiferson/btpath/git/tags{/sha}",
503 "git_refs_url": "https://api.github.com/repos/mreiferson/btpath/git/refs{/sha}",
504 "trees_url": "https://api.github.com/repos/mreiferson/btpath/git/trees{/sha}",
505 "statuses_url": "https://api.github.com/repos/mreiferson/btpath/statuses/{sha}",
506 "languages_url": "https://api.github.com/repos/mreiferson/btpath/languages",
507 "stargazers_url": "https://api.github.com/repos/mreiferson/btpath/stargazers",
508 "contributors_url": "https://api.github.com/repos/mreiferson/btpath/contributors",
509 "subscribers_url": "https://api.github.com/repos/mreiferson/btpath/subscribers",
510 "subscription_url": "https://api.github.com/repos/mreiferson/btpath/subscription",
511 "commits_url": "https://api.github.com/repos/mreiferson/btpath/commits{/sha}",
512 "git_commits_url": "https://api.github.com/repos/mreiferson/btpath/git/commits{/sha}",
513 "comments_url": "https://api.github.com/repos/mreiferson/btpath/comments{/number}",
514 "issue_comment_url": "https://api.github.com/repos/mreiferson/btpath/issues/comments/{number}",
515 "contents_url": "https://api.github.com/repos/mreiferson/btpath/contents/{+path}",
516 "compare_url": "https://api.github.com/repos/mreiferson/btpath/compare/{base}...{head}",
517 "merges_url": "https://api.github.com/repos/mreiferson/btpath/merges",
518 "archive_url": "https://api.github.com/repos/mreiferson/btpath/{archive_format}{/ref}",
519 "downloads_url": "https://api.github.com/repos/mreiferson/btpath/downloads",
520 "issues_url": "https://api.github.com/repos/mreiferson/btpath/issues{/number}",
521 "pulls_url": "https://api.github.com/repos/mreiferson/btpath/pulls{/number}",
522 "milestones_url": "https://api.github.com/repos/mreiferson/btpath/milestones{/number}",
523 "notifications_url": "https://api.github.com/repos/mreiferson/btpath/notifications{?since,all,participating}",
524 "labels_url": "https://api.github.com/repos/mreiferson/btpath/labels{/name}",
525 "releases_url": "https://api.github.com/repos/mreiferson/btpath/releases{/id}",
526 "created_at": "2011-11-27T17:23:02Z",
527 "updated_at": "2013-01-04T17:58:42Z",
528 "pushed_at": "2011-11-29T01:36:49Z",
529 "git_url": "git://github.com/mreiferson/btpath.git",
530 "ssh_url": "git@github.com:mreiferson/btpath.git",
531 "clone_url": "https://github.com/mreiferson/btpath.git",
532 "svn_url": "https://github.com/mreiferson/btpath",
533 "homepage": "",
534 "size": 88,
535 "stargazers_count": 1,
536 "watchers_count": 1,
537 "language": "C++",
538 "has_issues": true,
539 "has_downloads": true,
540 "has_wiki": true,
541 "forks_count": 0,
542 "mirror_url": null,
543 "open_issues_count": 0,
544 "forks": 0,
545 "open_issues": 0,
546 "watchers": 1,
547 "default_branch": "master"
548 },
549 {
550 "id": 15747148,
551 "name": "chef-nsq",
552 "full_name": "mreiferson/chef-nsq",
553 "owner": {
554 "login": "mreiferson",
555 "id": 187441,
556 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
557 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
558 "url": "https://api.github.com/users/mreiferson",
559 "html_url": "https://github.com/mreiferson",
560 "followers_url": "https://api.github.com/users/mreiferson/followers",
561 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
562 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
563 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
564 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
565 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
566 "repos_url": "https://api.github.com/users/mreiferson/repos",
567 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
568 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
569 "type": "User",
570 "site_admin": false
571 },
572 "private": false,
573 "html_url": "https://github.com/mreiferson/chef-nsq",
574 "description": "Chef Cookbook for NSQ",
575 "fork": true,
576 "url": "https://api.github.com/repos/mreiferson/chef-nsq",
577 "forks_url": "https://api.github.com/repos/mreiferson/chef-nsq/forks",
578 "keys_url": "https://api.github.com/repos/mreiferson/chef-nsq/keys{/key_id}",
579 "collaborators_url": "https://api.github.com/repos/mreiferson/chef-nsq/collaborators{/collaborator}",
580 "teams_url": "https://api.github.com/repos/mreiferson/chef-nsq/teams",
581 "hooks_url": "https://api.github.com/repos/mreiferson/chef-nsq/hooks",
582 "issue_events_url": "https://api.github.com/repos/mreiferson/chef-nsq/issues/events{/number}",
583 "events_url": "https://api.github.com/repos/mreiferson/chef-nsq/events",
584 "assignees_url": "https://api.github.com/repos/mreiferson/chef-nsq/assignees{/user}",
585 "branches_url": "https://api.github.com/repos/mreiferson/chef-nsq/branches{/branch}",
586 "tags_url": "https://api.github.com/repos/mreiferson/chef-nsq/tags",
587 "blobs_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/blobs{/sha}",
588 "git_tags_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/tags{/sha}",
589 "git_refs_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/refs{/sha}",
590 "trees_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/trees{/sha}",
591 "statuses_url": "https://api.github.com/repos/mreiferson/chef-nsq/statuses/{sha}",
592 "languages_url": "https://api.github.com/repos/mreiferson/chef-nsq/languages",
593 "stargazers_url": "https://api.github.com/repos/mreiferson/chef-nsq/stargazers",
594 "contributors_url": "https://api.github.com/repos/mreiferson/chef-nsq/contributors",
595 "subscribers_url": "https://api.github.com/repos/mreiferson/chef-nsq/subscribers",
596 "subscription_url": "https://api.github.com/repos/mreiferson/chef-nsq/subscription",
597 "commits_url": "https://api.github.com/repos/mreiferson/chef-nsq/commits{/sha}",
598 "git_commits_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/commits{/sha}",
599 "comments_url": "https://api.github.com/repos/mreiferson/chef-nsq/comments{/number}",
600 "issue_comment_url": "https://api.github.com/repos/mreiferson/chef-nsq/issues/comments/{number}",
601 "contents_url": "https://api.github.com/repos/mreiferson/chef-nsq/contents/{+path}",
602 "compare_url": "https://api.github.com/repos/mreiferson/chef-nsq/compare/{base}...{head}",
603 "merges_url": "https://api.github.com/repos/mreiferson/chef-nsq/merges",
604 "archive_url": "https://api.github.com/repos/mreiferson/chef-nsq/{archive_format}{/ref}",
605 "downloads_url": "https://api.github.com/repos/mreiferson/chef-nsq/downloads",
606 "issues_url": "https://api.github.com/repos/mreiferson/chef-nsq/issues{/number}",
607 "pulls_url": "https://api.github.com/repos/mreiferson/chef-nsq/pulls{/number}",
608 "milestones_url": "https://api.github.com/repos/mreiferson/chef-nsq/milestones{/number}",
609 "notifications_url": "https://api.github.com/repos/mreiferson/chef-nsq/notifications{?since,all,participating}",
610 "labels_url": "https://api.github.com/repos/mreiferson/chef-nsq/labels{/name}",
611 "releases_url": "https://api.github.com/repos/mreiferson/chef-nsq/releases{/id}",
612 "created_at": "2014-01-08T20:27:41Z",
613 "updated_at": "2014-04-28T14:15:50Z",
614 "pushed_at": "2014-04-28T04:31:58Z",
615 "git_url": "git://github.com/mreiferson/chef-nsq.git",
616 "ssh_url": "git@github.com:mreiferson/chef-nsq.git",
617 "clone_url": "https://github.com/mreiferson/chef-nsq.git",
618 "svn_url": "https://github.com/mreiferson/chef-nsq",
619 "homepage": null,
620 "size": 132,
621 "stargazers_count": 0,
622 "watchers_count": 0,
623 "language": "Ruby",
624 "has_issues": false,
625 "has_downloads": true,
626 "has_wiki": false,
627 "forks_count": 0,
628 "mirror_url": null,
629 "open_issues_count": 0,
630 "forks": 0,
631 "open_issues": 0,
632 "watchers": 0,
633 "default_branch": "master"
634 },
635 {
636 "id": 5287337,
637 "name": "dablooms",
638 "full_name": "mreiferson/dablooms",
639 "owner": {
640 "login": "mreiferson",
641 "id": 187441,
642 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
643 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
644 "url": "https://api.github.com/users/mreiferson",
645 "html_url": "https://github.com/mreiferson",
646 "followers_url": "https://api.github.com/users/mreiferson/followers",
647 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
648 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
649 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
650 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
651 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
652 "repos_url": "https://api.github.com/users/mreiferson/repos",
653 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
654 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
655 "type": "User",
656 "site_admin": false
657 },
658 "private": false,
659 "html_url": "https://github.com/mreiferson/dablooms",
660 "description": "scaling, counting, bloom filter library",
661 "fork": true,
662 "url": "https://api.github.com/repos/mreiferson/dablooms",
663 "forks_url": "https://api.github.com/repos/mreiferson/dablooms/forks",
664 "keys_url": "https://api.github.com/repos/mreiferson/dablooms/keys{/key_id}",
665 "collaborators_url": "https://api.github.com/repos/mreiferson/dablooms/collaborators{/collaborator}",
666 "teams_url": "https://api.github.com/repos/mreiferson/dablooms/teams",
667 "hooks_url": "https://api.github.com/repos/mreiferson/dablooms/hooks",
668 "issue_events_url": "https://api.github.com/repos/mreiferson/dablooms/issues/events{/number}",
669 "events_url": "https://api.github.com/repos/mreiferson/dablooms/events",
670 "assignees_url": "https://api.github.com/repos/mreiferson/dablooms/assignees{/user}",
671 "branches_url": "https://api.github.com/repos/mreiferson/dablooms/branches{/branch}",
672 "tags_url": "https://api.github.com/repos/mreiferson/dablooms/tags",
673 "blobs_url": "https://api.github.com/repos/mreiferson/dablooms/git/blobs{/sha}",
674 "git_tags_url": "https://api.github.com/repos/mreiferson/dablooms/git/tags{/sha}",
675 "git_refs_url": "https://api.github.com/repos/mreiferson/dablooms/git/refs{/sha}",
676 "trees_url": "https://api.github.com/repos/mreiferson/dablooms/git/trees{/sha}",
677 "statuses_url": "https://api.github.com/repos/mreiferson/dablooms/statuses/{sha}",
678 "languages_url": "https://api.github.com/repos/mreiferson/dablooms/languages",
679 "stargazers_url": "https://api.github.com/repos/mreiferson/dablooms/stargazers",
680 "contributors_url": "https://api.github.com/repos/mreiferson/dablooms/contributors",
681 "subscribers_url": "https://api.github.com/repos/mreiferson/dablooms/subscribers",
682 "subscription_url": "https://api.github.com/repos/mreiferson/dablooms/subscription",
683 "commits_url": "https://api.github.com/repos/mreiferson/dablooms/commits{/sha}",
684 "git_commits_url": "https://api.github.com/repos/mreiferson/dablooms/git/commits{/sha}",
685 "comments_url": "https://api.github.com/repos/mreiferson/dablooms/comments{/number}",
686 "issue_comment_url": "https://api.github.com/repos/mreiferson/dablooms/issues/comments/{number}",
687 "contents_url": "https://api.github.com/repos/mreiferson/dablooms/contents/{+path}",
688 "compare_url": "https://api.github.com/repos/mreiferson/dablooms/compare/{base}...{head}",
689 "merges_url": "https://api.github.com/repos/mreiferson/dablooms/merges",
690 "archive_url": "https://api.github.com/repos/mreiferson/dablooms/{archive_format}{/ref}",
691 "downloads_url": "https://api.github.com/repos/mreiferson/dablooms/downloads",
692 "issues_url": "https://api.github.com/repos/mreiferson/dablooms/issues{/number}",
693 "pulls_url": "https://api.github.com/repos/mreiferson/dablooms/pulls{/number}",
694 "milestones_url": "https://api.github.com/repos/mreiferson/dablooms/milestones{/number}",
695 "notifications_url": "https://api.github.com/repos/mreiferson/dablooms/notifications{?since,all,participating}",
696 "labels_url": "https://api.github.com/repos/mreiferson/dablooms/labels{/name}",
697 "releases_url": "https://api.github.com/repos/mreiferson/dablooms/releases{/id}",
698 "created_at": "2012-08-03T16:03:50Z",
699 "updated_at": "2013-03-08T15:37:44Z",
700 "pushed_at": "2013-03-08T15:37:44Z",
701 "git_url": "git://github.com/mreiferson/dablooms.git",
702 "ssh_url": "git@github.com:mreiferson/dablooms.git",
703 "clone_url": "https://github.com/mreiferson/dablooms.git",
704 "svn_url": "https://github.com/mreiferson/dablooms",
705 "homepage": "",
706 "size": 186,
707 "stargazers_count": 1,
708 "watchers_count": 1,
709 "language": "C",
710 "has_issues": false,
711 "has_downloads": true,
712 "has_wiki": true,
713 "forks_count": 0,
714 "mirror_url": null,
715 "open_issues_count": 0,
716 "forks": 0,
717 "open_issues": 0,
718 "watchers": 1,
719 "default_branch": "master"
720 },
721 {
722 "id": 2861959,
723 "name": "dod",
724 "full_name": "mreiferson/dod",
725 "owner": {
726 "login": "mreiferson",
727 "id": 187441,
728 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
729 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
730 "url": "https://api.github.com/users/mreiferson",
731 "html_url": "https://github.com/mreiferson",
732 "followers_url": "https://api.github.com/users/mreiferson/followers",
733 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
734 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
735 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
736 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
737 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
738 "repos_url": "https://api.github.com/users/mreiferson/repos",
739 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
740 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
741 "type": "User",
742 "site_admin": false
743 },
744 "private": false,
745 "html_url": "https://github.com/mreiferson/dod",
746 "description": "Do or Die - an incomplete real-time strategy game inspired by Warcraft (1997)",
747 "fork": false,
748 "url": "https://api.github.com/repos/mreiferson/dod",
749 "forks_url": "https://api.github.com/repos/mreiferson/dod/forks",
750 "keys_url": "https://api.github.com/repos/mreiferson/dod/keys{/key_id}",
751 "collaborators_url": "https://api.github.com/repos/mreiferson/dod/collaborators{/collaborator}",
752 "teams_url": "https://api.github.com/repos/mreiferson/dod/teams",
753 "hooks_url": "https://api.github.com/repos/mreiferson/dod/hooks",
754 "issue_events_url": "https://api.github.com/repos/mreiferson/dod/issues/events{/number}",
755 "events_url": "https://api.github.com/repos/mreiferson/dod/events",
756 "assignees_url": "https://api.github.com/repos/mreiferson/dod/assignees{/user}",
757 "branches_url": "https://api.github.com/repos/mreiferson/dod/branches{/branch}",
758 "tags_url": "https://api.github.com/repos/mreiferson/dod/tags",
759 "blobs_url": "https://api.github.com/repos/mreiferson/dod/git/blobs{/sha}",
760 "git_tags_url": "https://api.github.com/repos/mreiferson/dod/git/tags{/sha}",
761 "git_refs_url": "https://api.github.com/repos/mreiferson/dod/git/refs{/sha}",
762 "trees_url": "https://api.github.com/repos/mreiferson/dod/git/trees{/sha}",
763 "statuses_url": "https://api.github.com/repos/mreiferson/dod/statuses/{sha}",
764 "languages_url": "https://api.github.com/repos/mreiferson/dod/languages",
765 "stargazers_url": "https://api.github.com/repos/mreiferson/dod/stargazers",
766 "contributors_url": "https://api.github.com/repos/mreiferson/dod/contributors",
767 "subscribers_url": "https://api.github.com/repos/mreiferson/dod/subscribers",
768 "subscription_url": "https://api.github.com/repos/mreiferson/dod/subscription",
769 "commits_url": "https://api.github.com/repos/mreiferson/dod/commits{/sha}",
770 "git_commits_url": "https://api.github.com/repos/mreiferson/dod/git/commits{/sha}",
771 "comments_url": "https://api.github.com/repos/mreiferson/dod/comments{/number}",
772 "issue_comment_url": "https://api.github.com/repos/mreiferson/dod/issues/comments/{number}",
773 "contents_url": "https://api.github.com/repos/mreiferson/dod/contents/{+path}",
774 "compare_url": "https://api.github.com/repos/mreiferson/dod/compare/{base}...{head}",
775 "merges_url": "https://api.github.com/repos/mreiferson/dod/merges",
776 "archive_url": "https://api.github.com/repos/mreiferson/dod/{archive_format}{/ref}",
777 "downloads_url": "https://api.github.com/repos/mreiferson/dod/downloads",
778 "issues_url": "https://api.github.com/repos/mreiferson/dod/issues{/number}",
779 "pulls_url": "https://api.github.com/repos/mreiferson/dod/pulls{/number}",
780 "milestones_url": "https://api.github.com/repos/mreiferson/dod/milestones{/number}",
781 "notifications_url": "https://api.github.com/repos/mreiferson/dod/notifications{?since,all,participating}",
782 "labels_url": "https://api.github.com/repos/mreiferson/dod/labels{/name}",
783 "releases_url": "https://api.github.com/repos/mreiferson/dod/releases{/id}",
784 "created_at": "2011-11-27T17:33:19Z",
785 "updated_at": "2014-05-13T00:56:53Z",
786 "pushed_at": "2011-11-29T02:08:57Z",
787 "git_url": "git://github.com/mreiferson/dod.git",
788 "ssh_url": "git@github.com:mreiferson/dod.git",
789 "clone_url": "https://github.com/mreiferson/dod.git",
790 "svn_url": "https://github.com/mreiferson/dod",
791 "homepage": "",
792 "size": 2044,
793 "stargazers_count": 1,
794 "watchers_count": 1,
795 "language": "C++",
796 "has_issues": true,
797 "has_downloads": true,
798 "has_wiki": true,
799 "forks_count": 0,
800 "mirror_url": null,
801 "open_issues_count": 0,
802 "forks": 0,
803 "open_issues": 0,
804 "watchers": 1,
805 "default_branch": "master"
806 },
807 {
808 "id": 4515792,
809 "name": "doozer",
810 "full_name": "mreiferson/doozer",
811 "owner": {
812 "login": "mreiferson",
813 "id": 187441,
814 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
815 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
816 "url": "https://api.github.com/users/mreiferson",
817 "html_url": "https://github.com/mreiferson",
818 "followers_url": "https://api.github.com/users/mreiferson/followers",
819 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
820 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
821 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
822 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
823 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
824 "repos_url": "https://api.github.com/users/mreiferson/repos",
825 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
826 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
827 "type": "User",
828 "site_admin": false
829 },
830 "private": false,
831 "html_url": "https://github.com/mreiferson/doozer",
832 "description": "Go client driver for doozerd, a consistent, distributed data store",
833 "fork": true,
834 "url": "https://api.github.com/repos/mreiferson/doozer",
835 "forks_url": "https://api.github.com/repos/mreiferson/doozer/forks",
836 "keys_url": "https://api.github.com/repos/mreiferson/doozer/keys{/key_id}",
837 "collaborators_url": "https://api.github.com/repos/mreiferson/doozer/collaborators{/collaborator}",
838 "teams_url": "https://api.github.com/repos/mreiferson/doozer/teams",
839 "hooks_url": "https://api.github.com/repos/mreiferson/doozer/hooks",
840 "issue_events_url": "https://api.github.com/repos/mreiferson/doozer/issues/events{/number}",
841 "events_url": "https://api.github.com/repos/mreiferson/doozer/events",
842 "assignees_url": "https://api.github.com/repos/mreiferson/doozer/assignees{/user}",
843 "branches_url": "https://api.github.com/repos/mreiferson/doozer/branches{/branch}",
844 "tags_url": "https://api.github.com/repos/mreiferson/doozer/tags",
845 "blobs_url": "https://api.github.com/repos/mreiferson/doozer/git/blobs{/sha}",
846 "git_tags_url": "https://api.github.com/repos/mreiferson/doozer/git/tags{/sha}",
847 "git_refs_url": "https://api.github.com/repos/mreiferson/doozer/git/refs{/sha}",
848 "trees_url": "https://api.github.com/repos/mreiferson/doozer/git/trees{/sha}",
849 "statuses_url": "https://api.github.com/repos/mreiferson/doozer/statuses/{sha}",
850 "languages_url": "https://api.github.com/repos/mreiferson/doozer/languages",
851 "stargazers_url": "https://api.github.com/repos/mreiferson/doozer/stargazers",
852 "contributors_url": "https://api.github.com/repos/mreiferson/doozer/contributors",
853 "subscribers_url": "https://api.github.com/repos/mreiferson/doozer/subscribers",
854 "subscription_url": "https://api.github.com/repos/mreiferson/doozer/subscription",
855 "commits_url": "https://api.github.com/repos/mreiferson/doozer/commits{/sha}",
856 "git_commits_url": "https://api.github.com/repos/mreiferson/doozer/git/commits{/sha}",
857 "comments_url": "https://api.github.com/repos/mreiferson/doozer/comments{/number}",
858 "issue_comment_url": "https://api.github.com/repos/mreiferson/doozer/issues/comments/{number}",
859 "contents_url": "https://api.github.com/repos/mreiferson/doozer/contents/{+path}",
860 "compare_url": "https://api.github.com/repos/mreiferson/doozer/compare/{base}...{head}",
861 "merges_url": "https://api.github.com/repos/mreiferson/doozer/merges",
862 "archive_url": "https://api.github.com/repos/mreiferson/doozer/{archive_format}{/ref}",
863 "downloads_url": "https://api.github.com/repos/mreiferson/doozer/downloads",
864 "issues_url": "https://api.github.com/repos/mreiferson/doozer/issues{/number}",
865 "pulls_url": "https://api.github.com/repos/mreiferson/doozer/pulls{/number}",
866 "milestones_url": "https://api.github.com/repos/mreiferson/doozer/milestones{/number}",
867 "notifications_url": "https://api.github.com/repos/mreiferson/doozer/notifications{?since,all,participating}",
868 "labels_url": "https://api.github.com/repos/mreiferson/doozer/labels{/name}",
869 "releases_url": "https://api.github.com/repos/mreiferson/doozer/releases{/id}",
870 "created_at": "2012-06-01T03:41:14Z",
871 "updated_at": "2013-03-16T15:23:56Z",
872 "pushed_at": "2013-03-16T15:23:55Z",
873 "git_url": "git://github.com/mreiferson/doozer.git",
874 "ssh_url": "git@github.com:mreiferson/doozer.git",
875 "clone_url": "https://github.com/mreiferson/doozer.git",
876 "svn_url": "https://github.com/mreiferson/doozer",
877 "homepage": "https://github.com/ha/doozerd",
878 "size": 2584,
879 "stargazers_count": 0,
880 "watchers_count": 0,
881 "language": "Go",
882 "has_issues": false,
883 "has_downloads": true,
884 "has_wiki": true,
885 "forks_count": 0,
886 "mirror_url": null,
887 "open_issues_count": 0,
888 "forks": 0,
889 "open_issues": 0,
890 "watchers": 0,
891 "default_branch": "master"
892 },
893 {
894 "id": 3391437,
895 "name": "doozer-c",
896 "full_name": "mreiferson/doozer-c",
897 "owner": {
898 "login": "mreiferson",
899 "id": 187441,
900 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
901 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
902 "url": "https://api.github.com/users/mreiferson",
903 "html_url": "https://github.com/mreiferson",
904 "followers_url": "https://api.github.com/users/mreiferson/followers",
905 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
906 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
907 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
908 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
909 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
910 "repos_url": "https://api.github.com/users/mreiferson/repos",
911 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
912 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
913 "type": "User",
914 "site_admin": false
915 },
916 "private": false,
917 "html_url": "https://github.com/mreiferson/doozer-c",
918 "description": "async C client library for doozerd",
919 "fork": true,
920 "url": "https://api.github.com/repos/mreiferson/doozer-c",
921 "forks_url": "https://api.github.com/repos/mreiferson/doozer-c/forks",
922 "keys_url": "https://api.github.com/repos/mreiferson/doozer-c/keys{/key_id}",
923 "collaborators_url": "https://api.github.com/repos/mreiferson/doozer-c/collaborators{/collaborator}",
924 "teams_url": "https://api.github.com/repos/mreiferson/doozer-c/teams",
925 "hooks_url": "https://api.github.com/repos/mreiferson/doozer-c/hooks",
926 "issue_events_url": "https://api.github.com/repos/mreiferson/doozer-c/issues/events{/number}",
927 "events_url": "https://api.github.com/repos/mreiferson/doozer-c/events",
928 "assignees_url": "https://api.github.com/repos/mreiferson/doozer-c/assignees{/user}",
929 "branches_url": "https://api.github.com/repos/mreiferson/doozer-c/branches{/branch}",
930 "tags_url": "https://api.github.com/repos/mreiferson/doozer-c/tags",
931 "blobs_url": "https://api.github.com/repos/mreiferson/doozer-c/git/blobs{/sha}",
932 "git_tags_url": "https://api.github.com/repos/mreiferson/doozer-c/git/tags{/sha}",
933 "git_refs_url": "https://api.github.com/repos/mreiferson/doozer-c/git/refs{/sha}",
934 "trees_url": "https://api.github.com/repos/mreiferson/doozer-c/git/trees{/sha}",
935 "statuses_url": "https://api.github.com/repos/mreiferson/doozer-c/statuses/{sha}",
936 "languages_url": "https://api.github.com/repos/mreiferson/doozer-c/languages",
937 "stargazers_url": "https://api.github.com/repos/mreiferson/doozer-c/stargazers",
938 "contributors_url": "https://api.github.com/repos/mreiferson/doozer-c/contributors",
939 "subscribers_url": "https://api.github.com/repos/mreiferson/doozer-c/subscribers",
940 "subscription_url": "https://api.github.com/repos/mreiferson/doozer-c/subscription",
941 "commits_url": "https://api.github.com/repos/mreiferson/doozer-c/commits{/sha}",
942 "git_commits_url": "https://api.github.com/repos/mreiferson/doozer-c/git/commits{/sha}",
943 "comments_url": "https://api.github.com/repos/mreiferson/doozer-c/comments{/number}",
944 "issue_comment_url": "https://api.github.com/repos/mreiferson/doozer-c/issues/comments/{number}",
945 "contents_url": "https://api.github.com/repos/mreiferson/doozer-c/contents/{+path}",
946 "compare_url": "https://api.github.com/repos/mreiferson/doozer-c/compare/{base}...{head}",
947 "merges_url": "https://api.github.com/repos/mreiferson/doozer-c/merges",
948 "archive_url": "https://api.github.com/repos/mreiferson/doozer-c/{archive_format}{/ref}",
949 "downloads_url": "https://api.github.com/repos/mreiferson/doozer-c/downloads",
950 "issues_url": "https://api.github.com/repos/mreiferson/doozer-c/issues{/number}",
951 "pulls_url": "https://api.github.com/repos/mreiferson/doozer-c/pulls{/number}",
952 "milestones_url": "https://api.github.com/repos/mreiferson/doozer-c/milestones{/number}",
953 "notifications_url": "https://api.github.com/repos/mreiferson/doozer-c/notifications{?since,all,participating}",
954 "labels_url": "https://api.github.com/repos/mreiferson/doozer-c/labels{/name}",
955 "releases_url": "https://api.github.com/repos/mreiferson/doozer-c/releases{/id}",
956 "created_at": "2012-02-08T21:15:33Z",
957 "updated_at": "2014-04-03T21:58:49Z",
958 "pushed_at": "2012-11-21T16:46:25Z",
959 "git_url": "git://github.com/mreiferson/doozer-c.git",
960 "ssh_url": "git@github.com:mreiferson/doozer-c.git",
961 "clone_url": "https://github.com/mreiferson/doozer-c.git",
962 "svn_url": "https://github.com/mreiferson/doozer-c",
963 "homepage": "",
964 "size": 158,
965 "stargazers_count": 0,
966 "watchers_count": 0,
967 "language": "C",
968 "has_issues": false,
969 "has_downloads": true,
970 "has_wiki": true,
971 "forks_count": 0,
972 "mirror_url": null,
973 "open_issues_count": 0,
974 "forks": 0,
975 "open_issues": 0,
976 "watchers": 0,
977 "default_branch": "master"
978 },
979 {
980 "id": 4515795,
981 "name": "doozerd",
982 "full_name": "mreiferson/doozerd",
983 "owner": {
984 "login": "mreiferson",
985 "id": 187441,
986 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
987 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
988 "url": "https://api.github.com/users/mreiferson",
989 "html_url": "https://github.com/mreiferson",
990 "followers_url": "https://api.github.com/users/mreiferson/followers",
991 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
992 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
993 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
994 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
995 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
996 "repos_url": "https://api.github.com/users/mreiferson/repos",
997 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
998 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
999 "type": "User",
1000 "site_admin": false
1001 },
1002 "private": false,
1003 "html_url": "https://github.com/mreiferson/doozerd",
1004 "description": "A consistent distributed data store.",
1005 "fork": true,
1006 "url": "https://api.github.com/repos/mreiferson/doozerd",
1007 "forks_url": "https://api.github.com/repos/mreiferson/doozerd/forks",
1008 "keys_url": "https://api.github.com/repos/mreiferson/doozerd/keys{/key_id}",
1009 "collaborators_url": "https://api.github.com/repos/mreiferson/doozerd/collaborators{/collaborator}",
1010 "teams_url": "https://api.github.com/repos/mreiferson/doozerd/teams",
1011 "hooks_url": "https://api.github.com/repos/mreiferson/doozerd/hooks",
1012 "issue_events_url": "https://api.github.com/repos/mreiferson/doozerd/issues/events{/number}",
1013 "events_url": "https://api.github.com/repos/mreiferson/doozerd/events",
1014 "assignees_url": "https://api.github.com/repos/mreiferson/doozerd/assignees{/user}",
1015 "branches_url": "https://api.github.com/repos/mreiferson/doozerd/branches{/branch}",
1016 "tags_url": "https://api.github.com/repos/mreiferson/doozerd/tags",
1017 "blobs_url": "https://api.github.com/repos/mreiferson/doozerd/git/blobs{/sha}",
1018 "git_tags_url": "https://api.github.com/repos/mreiferson/doozerd/git/tags{/sha}",
1019 "git_refs_url": "https://api.github.com/repos/mreiferson/doozerd/git/refs{/sha}",
1020 "trees_url": "https://api.github.com/repos/mreiferson/doozerd/git/trees{/sha}",
1021 "statuses_url": "https://api.github.com/repos/mreiferson/doozerd/statuses/{sha}",
1022 "languages_url": "https://api.github.com/repos/mreiferson/doozerd/languages",
1023 "stargazers_url": "https://api.github.com/repos/mreiferson/doozerd/stargazers",
1024 "contributors_url": "https://api.github.com/repos/mreiferson/doozerd/contributors",
1025 "subscribers_url": "https://api.github.com/repos/mreiferson/doozerd/subscribers",
1026 "subscription_url": "https://api.github.com/repos/mreiferson/doozerd/subscription",
1027 "commits_url": "https://api.github.com/repos/mreiferson/doozerd/commits{/sha}",
1028 "git_commits_url": "https://api.github.com/repos/mreiferson/doozerd/git/commits{/sha}",
1029 "comments_url": "https://api.github.com/repos/mreiferson/doozerd/comments{/number}",
1030 "issue_comment_url": "https://api.github.com/repos/mreiferson/doozerd/issues/comments/{number}",
1031 "contents_url": "https://api.github.com/repos/mreiferson/doozerd/contents/{+path}",
1032 "compare_url": "https://api.github.com/repos/mreiferson/doozerd/compare/{base}...{head}",
1033 "merges_url": "https://api.github.com/repos/mreiferson/doozerd/merges",
1034 "archive_url": "https://api.github.com/repos/mreiferson/doozerd/{archive_format}{/ref}",
1035 "downloads_url": "https://api.github.com/repos/mreiferson/doozerd/downloads",
1036 "issues_url": "https://api.github.com/repos/mreiferson/doozerd/issues{/number}",
1037 "pulls_url": "https://api.github.com/repos/mreiferson/doozerd/pulls{/number}",
1038 "milestones_url": "https://api.github.com/repos/mreiferson/doozerd/milestones{/number}",
1039 "notifications_url": "https://api.github.com/repos/mreiferson/doozerd/notifications{?since,all,participating}",
1040 "labels_url": "https://api.github.com/repos/mreiferson/doozerd/labels{/name}",
1041 "releases_url": "https://api.github.com/repos/mreiferson/doozerd/releases{/id}",
1042 "created_at": "2012-06-01T03:41:32Z",
1043 "updated_at": "2013-12-28T19:22:30Z",
1044 "pushed_at": "2013-12-28T19:22:30Z",
1045 "git_url": "git://github.com/mreiferson/doozerd.git",
1046 "ssh_url": "git@github.com:mreiferson/doozerd.git",
1047 "clone_url": "https://github.com/mreiferson/doozerd.git",
1048 "svn_url": "https://github.com/mreiferson/doozerd",
1049 "homepage": "",
1050 "size": 3135,
1051 "stargazers_count": 0,
1052 "watchers_count": 0,
1053 "language": "Go",
1054 "has_issues": false,
1055 "has_downloads": true,
1056 "has_wiki": true,
1057 "forks_count": 0,
1058 "mirror_url": null,
1059 "open_issues_count": 0,
1060 "forks": 0,
1061 "open_issues": 0,
1062 "watchers": 0,
1063 "default_branch": "master"
1064 },
1065 {
1066 "id": 8172002,
1067 "name": "e",
1068 "full_name": "mreiferson/e",
1069 "owner": {
1070 "login": "mreiferson",
1071 "id": 187441,
1072 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1073 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1074 "url": "https://api.github.com/users/mreiferson",
1075 "html_url": "https://github.com/mreiferson",
1076 "followers_url": "https://api.github.com/users/mreiferson/followers",
1077 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1078 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1079 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1080 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1081 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1082 "repos_url": "https://api.github.com/users/mreiferson/repos",
1083 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1084 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1085 "type": "User",
1086 "site_admin": false
1087 },
1088 "private": false,
1089 "html_url": "https://github.com/mreiferson/e",
1090 "description": "Library containing high-performance datastructures and utilities for C++",
1091 "fork": true,
1092 "url": "https://api.github.com/repos/mreiferson/e",
1093 "forks_url": "https://api.github.com/repos/mreiferson/e/forks",
1094 "keys_url": "https://api.github.com/repos/mreiferson/e/keys{/key_id}",
1095 "collaborators_url": "https://api.github.com/repos/mreiferson/e/collaborators{/collaborator}",
1096 "teams_url": "https://api.github.com/repos/mreiferson/e/teams",
1097 "hooks_url": "https://api.github.com/repos/mreiferson/e/hooks",
1098 "issue_events_url": "https://api.github.com/repos/mreiferson/e/issues/events{/number}",
1099 "events_url": "https://api.github.com/repos/mreiferson/e/events",
1100 "assignees_url": "https://api.github.com/repos/mreiferson/e/assignees{/user}",
1101 "branches_url": "https://api.github.com/repos/mreiferson/e/branches{/branch}",
1102 "tags_url": "https://api.github.com/repos/mreiferson/e/tags",
1103 "blobs_url": "https://api.github.com/repos/mreiferson/e/git/blobs{/sha}",
1104 "git_tags_url": "https://api.github.com/repos/mreiferson/e/git/tags{/sha}",
1105 "git_refs_url": "https://api.github.com/repos/mreiferson/e/git/refs{/sha}",
1106 "trees_url": "https://api.github.com/repos/mreiferson/e/git/trees{/sha}",
1107 "statuses_url": "https://api.github.com/repos/mreiferson/e/statuses/{sha}",
1108 "languages_url": "https://api.github.com/repos/mreiferson/e/languages",
1109 "stargazers_url": "https://api.github.com/repos/mreiferson/e/stargazers",
1110 "contributors_url": "https://api.github.com/repos/mreiferson/e/contributors",
1111 "subscribers_url": "https://api.github.com/repos/mreiferson/e/subscribers",
1112 "subscription_url": "https://api.github.com/repos/mreiferson/e/subscription",
1113 "commits_url": "https://api.github.com/repos/mreiferson/e/commits{/sha}",
1114 "git_commits_url": "https://api.github.com/repos/mreiferson/e/git/commits{/sha}",
1115 "comments_url": "https://api.github.com/repos/mreiferson/e/comments{/number}",
1116 "issue_comment_url": "https://api.github.com/repos/mreiferson/e/issues/comments/{number}",
1117 "contents_url": "https://api.github.com/repos/mreiferson/e/contents/{+path}",
1118 "compare_url": "https://api.github.com/repos/mreiferson/e/compare/{base}...{head}",
1119 "merges_url": "https://api.github.com/repos/mreiferson/e/merges",
1120 "archive_url": "https://api.github.com/repos/mreiferson/e/{archive_format}{/ref}",
1121 "downloads_url": "https://api.github.com/repos/mreiferson/e/downloads",
1122 "issues_url": "https://api.github.com/repos/mreiferson/e/issues{/number}",
1123 "pulls_url": "https://api.github.com/repos/mreiferson/e/pulls{/number}",
1124 "milestones_url": "https://api.github.com/repos/mreiferson/e/milestones{/number}",
1125 "notifications_url": "https://api.github.com/repos/mreiferson/e/notifications{?since,all,participating}",
1126 "labels_url": "https://api.github.com/repos/mreiferson/e/labels{/name}",
1127 "releases_url": "https://api.github.com/repos/mreiferson/e/releases{/id}",
1128 "created_at": "2013-02-13T02:42:55Z",
1129 "updated_at": "2013-02-18T21:10:07Z",
1130 "pushed_at": "2013-02-13T02:45:16Z",
1131 "git_url": "git://github.com/mreiferson/e.git",
1132 "ssh_url": "git@github.com:mreiferson/e.git",
1133 "clone_url": "https://github.com/mreiferson/e.git",
1134 "svn_url": "https://github.com/mreiferson/e",
1135 "homepage": "",
1136 "size": 437,
1137 "stargazers_count": 0,
1138 "watchers_count": 0,
1139 "language": "C++",
1140 "has_issues": false,
1141 "has_downloads": true,
1142 "has_wiki": true,
1143 "forks_count": 0,
1144 "mirror_url": null,
1145 "open_issues_count": 0,
1146 "forks": 0,
1147 "open_issues": 0,
1148 "watchers": 0,
1149 "default_branch": "master"
1150 },
1151 {
1152 "id": 2792604,
1153 "name": "encfs-macfusion2",
1154 "full_name": "mreiferson/encfs-macfusion2",
1155 "owner": {
1156 "login": "mreiferson",
1157 "id": 187441,
1158 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1159 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1160 "url": "https://api.github.com/users/mreiferson",
1161 "html_url": "https://github.com/mreiferson",
1162 "followers_url": "https://api.github.com/users/mreiferson/followers",
1163 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1164 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1165 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1166 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1167 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1168 "repos_url": "https://api.github.com/users/mreiferson/repos",
1169 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1170 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1171 "type": "User",
1172 "site_admin": false
1173 },
1174 "private": false,
1175 "html_url": "https://github.com/mreiferson/encfs-macfusion2",
1176 "description": "enhanced version of encfs-macfusion2 plugin http://code.google.com/p/encfs-macfusion2/",
1177 "fork": false,
1178 "url": "https://api.github.com/repos/mreiferson/encfs-macfusion2",
1179 "forks_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/forks",
1180 "keys_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/keys{/key_id}",
1181 "collaborators_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/collaborators{/collaborator}",
1182 "teams_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/teams",
1183 "hooks_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/hooks",
1184 "issue_events_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/issues/events{/number}",
1185 "events_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/events",
1186 "assignees_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/assignees{/user}",
1187 "branches_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/branches{/branch}",
1188 "tags_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/tags",
1189 "blobs_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/blobs{/sha}",
1190 "git_tags_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/tags{/sha}",
1191 "git_refs_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/refs{/sha}",
1192 "trees_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/trees{/sha}",
1193 "statuses_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/statuses/{sha}",
1194 "languages_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/languages",
1195 "stargazers_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/stargazers",
1196 "contributors_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/contributors",
1197 "subscribers_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/subscribers",
1198 "subscription_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/subscription",
1199 "commits_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/commits{/sha}",
1200 "git_commits_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/commits{/sha}",
1201 "comments_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/comments{/number}",
1202 "issue_comment_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/issues/comments/{number}",
1203 "contents_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/contents/{+path}",
1204 "compare_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/compare/{base}...{head}",
1205 "merges_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/merges",
1206 "archive_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/{archive_format}{/ref}",
1207 "downloads_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/downloads",
1208 "issues_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/issues{/number}",
1209 "pulls_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/pulls{/number}",
1210 "milestones_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/milestones{/number}",
1211 "notifications_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/notifications{?since,all,participating}",
1212 "labels_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/labels{/name}",
1213 "releases_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/releases{/id}",
1214 "created_at": "2011-11-17T01:58:01Z",
1215 "updated_at": "2013-10-22T06:29:03Z",
1216 "pushed_at": "2011-11-17T02:13:15Z",
1217 "git_url": "git://github.com/mreiferson/encfs-macfusion2.git",
1218 "ssh_url": "git@github.com:mreiferson/encfs-macfusion2.git",
1219 "clone_url": "https://github.com/mreiferson/encfs-macfusion2.git",
1220 "svn_url": "https://github.com/mreiferson/encfs-macfusion2",
1221 "homepage": "",
1222 "size": 195,
1223 "stargazers_count": 4,
1224 "watchers_count": 4,
1225 "language": "Objective-C",
1226 "has_issues": true,
1227 "has_downloads": true,
1228 "has_wiki": true,
1229 "forks_count": 1,
1230 "mirror_url": null,
1231 "open_issues_count": 0,
1232 "forks": 1,
1233 "open_issues": 0,
1234 "watchers": 4,
1235 "default_branch": "master"
1236 },
1237 {
1238 "id": 5263991,
1239 "name": "file2http",
1240 "full_name": "mreiferson/file2http",
1241 "owner": {
1242 "login": "mreiferson",
1243 "id": 187441,
1244 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1245 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1246 "url": "https://api.github.com/users/mreiferson",
1247 "html_url": "https://github.com/mreiferson",
1248 "followers_url": "https://api.github.com/users/mreiferson/followers",
1249 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1250 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1251 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1252 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1253 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1254 "repos_url": "https://api.github.com/users/mreiferson/repos",
1255 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1256 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1257 "type": "User",
1258 "site_admin": false
1259 },
1260 "private": false,
1261 "html_url": "https://github.com/mreiferson/file2http",
1262 "description": "spray a line-oriented file at an HTTP endpoint",
1263 "fork": true,
1264 "url": "https://api.github.com/repos/mreiferson/file2http",
1265 "forks_url": "https://api.github.com/repos/mreiferson/file2http/forks",
1266 "keys_url": "https://api.github.com/repos/mreiferson/file2http/keys{/key_id}",
1267 "collaborators_url": "https://api.github.com/repos/mreiferson/file2http/collaborators{/collaborator}",
1268 "teams_url": "https://api.github.com/repos/mreiferson/file2http/teams",
1269 "hooks_url": "https://api.github.com/repos/mreiferson/file2http/hooks",
1270 "issue_events_url": "https://api.github.com/repos/mreiferson/file2http/issues/events{/number}",
1271 "events_url": "https://api.github.com/repos/mreiferson/file2http/events",
1272 "assignees_url": "https://api.github.com/repos/mreiferson/file2http/assignees{/user}",
1273 "branches_url": "https://api.github.com/repos/mreiferson/file2http/branches{/branch}",
1274 "tags_url": "https://api.github.com/repos/mreiferson/file2http/tags",
1275 "blobs_url": "https://api.github.com/repos/mreiferson/file2http/git/blobs{/sha}",
1276 "git_tags_url": "https://api.github.com/repos/mreiferson/file2http/git/tags{/sha}",
1277 "git_refs_url": "https://api.github.com/repos/mreiferson/file2http/git/refs{/sha}",
1278 "trees_url": "https://api.github.com/repos/mreiferson/file2http/git/trees{/sha}",
1279 "statuses_url": "https://api.github.com/repos/mreiferson/file2http/statuses/{sha}",
1280 "languages_url": "https://api.github.com/repos/mreiferson/file2http/languages",
1281 "stargazers_url": "https://api.github.com/repos/mreiferson/file2http/stargazers",
1282 "contributors_url": "https://api.github.com/repos/mreiferson/file2http/contributors",
1283 "subscribers_url": "https://api.github.com/repos/mreiferson/file2http/subscribers",
1284 "subscription_url": "https://api.github.com/repos/mreiferson/file2http/subscription",
1285 "commits_url": "https://api.github.com/repos/mreiferson/file2http/commits{/sha}",
1286 "git_commits_url": "https://api.github.com/repos/mreiferson/file2http/git/commits{/sha}",
1287 "comments_url": "https://api.github.com/repos/mreiferson/file2http/comments{/number}",
1288 "issue_comment_url": "https://api.github.com/repos/mreiferson/file2http/issues/comments/{number}",
1289 "contents_url": "https://api.github.com/repos/mreiferson/file2http/contents/{+path}",
1290 "compare_url": "https://api.github.com/repos/mreiferson/file2http/compare/{base}...{head}",
1291 "merges_url": "https://api.github.com/repos/mreiferson/file2http/merges",
1292 "archive_url": "https://api.github.com/repos/mreiferson/file2http/{archive_format}{/ref}",
1293 "downloads_url": "https://api.github.com/repos/mreiferson/file2http/downloads",
1294 "issues_url": "https://api.github.com/repos/mreiferson/file2http/issues{/number}",
1295 "pulls_url": "https://api.github.com/repos/mreiferson/file2http/pulls{/number}",
1296 "milestones_url": "https://api.github.com/repos/mreiferson/file2http/milestones{/number}",
1297 "notifications_url": "https://api.github.com/repos/mreiferson/file2http/notifications{?since,all,participating}",
1298 "labels_url": "https://api.github.com/repos/mreiferson/file2http/labels{/name}",
1299 "releases_url": "https://api.github.com/repos/mreiferson/file2http/releases{/id}",
1300 "created_at": "2012-08-01T19:56:16Z",
1301 "updated_at": "2013-01-11T13:21:40Z",
1302 "pushed_at": "2012-12-21T15:44:32Z",
1303 "git_url": "git://github.com/mreiferson/file2http.git",
1304 "ssh_url": "git@github.com:mreiferson/file2http.git",
1305 "clone_url": "https://github.com/mreiferson/file2http.git",
1306 "svn_url": "https://github.com/mreiferson/file2http",
1307 "homepage": "",
1308 "size": 96,
1309 "stargazers_count": 1,
1310 "watchers_count": 1,
1311 "language": "Go",
1312 "has_issues": false,
1313 "has_downloads": true,
1314 "has_wiki": true,
1315 "forks_count": 0,
1316 "mirror_url": null,
1317 "open_issues_count": 0,
1318 "forks": 0,
1319 "open_issues": 0,
1320 "watchers": 1,
1321 "default_branch": "master"
1322 },
1323 {
1324 "id": 15291117,
1325 "name": "gablog",
1326 "full_name": "mreiferson/gablog",
1327 "owner": {
1328 "login": "mreiferson",
1329 "id": 187441,
1330 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1331 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1332 "url": "https://api.github.com/users/mreiferson",
1333 "html_url": "https://github.com/mreiferson",
1334 "followers_url": "https://api.github.com/users/mreiferson/followers",
1335 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1336 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1337 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1338 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1339 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1340 "repos_url": "https://api.github.com/users/mreiferson/repos",
1341 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1342 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1343 "type": "User",
1344 "site_admin": false
1345 },
1346 "private": false,
1347 "html_url": "https://github.com/mreiferson/gablog",
1348 "description": "Gopher Academy Blog -- fork of go.blog",
1349 "fork": true,
1350 "url": "https://api.github.com/repos/mreiferson/gablog",
1351 "forks_url": "https://api.github.com/repos/mreiferson/gablog/forks",
1352 "keys_url": "https://api.github.com/repos/mreiferson/gablog/keys{/key_id}",
1353 "collaborators_url": "https://api.github.com/repos/mreiferson/gablog/collaborators{/collaborator}",
1354 "teams_url": "https://api.github.com/repos/mreiferson/gablog/teams",
1355 "hooks_url": "https://api.github.com/repos/mreiferson/gablog/hooks",
1356 "issue_events_url": "https://api.github.com/repos/mreiferson/gablog/issues/events{/number}",
1357 "events_url": "https://api.github.com/repos/mreiferson/gablog/events",
1358 "assignees_url": "https://api.github.com/repos/mreiferson/gablog/assignees{/user}",
1359 "branches_url": "https://api.github.com/repos/mreiferson/gablog/branches{/branch}",
1360 "tags_url": "https://api.github.com/repos/mreiferson/gablog/tags",
1361 "blobs_url": "https://api.github.com/repos/mreiferson/gablog/git/blobs{/sha}",
1362 "git_tags_url": "https://api.github.com/repos/mreiferson/gablog/git/tags{/sha}",
1363 "git_refs_url": "https://api.github.com/repos/mreiferson/gablog/git/refs{/sha}",
1364 "trees_url": "https://api.github.com/repos/mreiferson/gablog/git/trees{/sha}",
1365 "statuses_url": "https://api.github.com/repos/mreiferson/gablog/statuses/{sha}",
1366 "languages_url": "https://api.github.com/repos/mreiferson/gablog/languages",
1367 "stargazers_url": "https://api.github.com/repos/mreiferson/gablog/stargazers",
1368 "contributors_url": "https://api.github.com/repos/mreiferson/gablog/contributors",
1369 "subscribers_url": "https://api.github.com/repos/mreiferson/gablog/subscribers",
1370 "subscription_url": "https://api.github.com/repos/mreiferson/gablog/subscription",
1371 "commits_url": "https://api.github.com/repos/mreiferson/gablog/commits{/sha}",
1372 "git_commits_url": "https://api.github.com/repos/mreiferson/gablog/git/commits{/sha}",
1373 "comments_url": "https://api.github.com/repos/mreiferson/gablog/comments{/number}",
1374 "issue_comment_url": "https://api.github.com/repos/mreiferson/gablog/issues/comments/{number}",
1375 "contents_url": "https://api.github.com/repos/mreiferson/gablog/contents/{+path}",
1376 "compare_url": "https://api.github.com/repos/mreiferson/gablog/compare/{base}...{head}",
1377 "merges_url": "https://api.github.com/repos/mreiferson/gablog/merges",
1378 "archive_url": "https://api.github.com/repos/mreiferson/gablog/{archive_format}{/ref}",
1379 "downloads_url": "https://api.github.com/repos/mreiferson/gablog/downloads",
1380 "issues_url": "https://api.github.com/repos/mreiferson/gablog/issues{/number}",
1381 "pulls_url": "https://api.github.com/repos/mreiferson/gablog/pulls{/number}",
1382 "milestones_url": "https://api.github.com/repos/mreiferson/gablog/milestones{/number}",
1383 "notifications_url": "https://api.github.com/repos/mreiferson/gablog/notifications{?since,all,participating}",
1384 "labels_url": "https://api.github.com/repos/mreiferson/gablog/labels{/name}",
1385 "releases_url": "https://api.github.com/repos/mreiferson/gablog/releases{/id}",
1386 "created_at": "2013-12-18T18:38:37Z",
1387 "updated_at": "2013-12-20T22:39:33Z",
1388 "pushed_at": "2013-12-20T22:21:52Z",
1389 "git_url": "git://github.com/mreiferson/gablog.git",
1390 "ssh_url": "git@github.com:mreiferson/gablog.git",
1391 "clone_url": "https://github.com/mreiferson/gablog.git",
1392 "svn_url": "https://github.com/mreiferson/gablog",
1393 "homepage": "http://blog.gopheracademy.com",
1394 "size": 7911,
1395 "stargazers_count": 0,
1396 "watchers_count": 0,
1397 "language": "CSS",
1398 "has_issues": false,
1399 "has_downloads": true,
1400 "has_wiki": true,
1401 "forks_count": 0,
1402 "mirror_url": null,
1403 "open_issues_count": 0,
1404 "forks": 0,
1405 "open_issues": 0,
1406 "watchers": 0,
1407 "default_branch": "master"
1408 },
1409 {
1410 "id": 12223286,
1411 "name": "git-open-pull",
1412 "full_name": "mreiferson/git-open-pull",
1413 "owner": {
1414 "login": "mreiferson",
1415 "id": 187441,
1416 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1417 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1418 "url": "https://api.github.com/users/mreiferson",
1419 "html_url": "https://github.com/mreiferson",
1420 "followers_url": "https://api.github.com/users/mreiferson/followers",
1421 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1422 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1423 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1424 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1425 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1426 "repos_url": "https://api.github.com/users/mreiferson/repos",
1427 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1428 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1429 "type": "User",
1430 "site_admin": false
1431 },
1432 "private": false,
1433 "html_url": "https://github.com/mreiferson/git-open-pull",
1434 "description": "convert a github issue into a pull request",
1435 "fork": true,
1436 "url": "https://api.github.com/repos/mreiferson/git-open-pull",
1437 "forks_url": "https://api.github.com/repos/mreiferson/git-open-pull/forks",
1438 "keys_url": "https://api.github.com/repos/mreiferson/git-open-pull/keys{/key_id}",
1439 "collaborators_url": "https://api.github.com/repos/mreiferson/git-open-pull/collaborators{/collaborator}",
1440 "teams_url": "https://api.github.com/repos/mreiferson/git-open-pull/teams",
1441 "hooks_url": "https://api.github.com/repos/mreiferson/git-open-pull/hooks",
1442 "issue_events_url": "https://api.github.com/repos/mreiferson/git-open-pull/issues/events{/number}",
1443 "events_url": "https://api.github.com/repos/mreiferson/git-open-pull/events",
1444 "assignees_url": "https://api.github.com/repos/mreiferson/git-open-pull/assignees{/user}",
1445 "branches_url": "https://api.github.com/repos/mreiferson/git-open-pull/branches{/branch}",
1446 "tags_url": "https://api.github.com/repos/mreiferson/git-open-pull/tags",
1447 "blobs_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/blobs{/sha}",
1448 "git_tags_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/tags{/sha}",
1449 "git_refs_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/refs{/sha}",
1450 "trees_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/trees{/sha}",
1451 "statuses_url": "https://api.github.com/repos/mreiferson/git-open-pull/statuses/{sha}",
1452 "languages_url": "https://api.github.com/repos/mreiferson/git-open-pull/languages",
1453 "stargazers_url": "https://api.github.com/repos/mreiferson/git-open-pull/stargazers",
1454 "contributors_url": "https://api.github.com/repos/mreiferson/git-open-pull/contributors",
1455 "subscribers_url": "https://api.github.com/repos/mreiferson/git-open-pull/subscribers",
1456 "subscription_url": "https://api.github.com/repos/mreiferson/git-open-pull/subscription",
1457 "commits_url": "https://api.github.com/repos/mreiferson/git-open-pull/commits{/sha}",
1458 "git_commits_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/commits{/sha}",
1459 "comments_url": "https://api.github.com/repos/mreiferson/git-open-pull/comments{/number}",
1460 "issue_comment_url": "https://api.github.com/repos/mreiferson/git-open-pull/issues/comments/{number}",
1461 "contents_url": "https://api.github.com/repos/mreiferson/git-open-pull/contents/{+path}",
1462 "compare_url": "https://api.github.com/repos/mreiferson/git-open-pull/compare/{base}...{head}",
1463 "merges_url": "https://api.github.com/repos/mreiferson/git-open-pull/merges",
1464 "archive_url": "https://api.github.com/repos/mreiferson/git-open-pull/{archive_format}{/ref}",
1465 "downloads_url": "https://api.github.com/repos/mreiferson/git-open-pull/downloads",
1466 "issues_url": "https://api.github.com/repos/mreiferson/git-open-pull/issues{/number}",
1467 "pulls_url": "https://api.github.com/repos/mreiferson/git-open-pull/pulls{/number}",
1468 "milestones_url": "https://api.github.com/repos/mreiferson/git-open-pull/milestones{/number}",
1469 "notifications_url": "https://api.github.com/repos/mreiferson/git-open-pull/notifications{?since,all,participating}",
1470 "labels_url": "https://api.github.com/repos/mreiferson/git-open-pull/labels{/name}",
1471 "releases_url": "https://api.github.com/repos/mreiferson/git-open-pull/releases{/id}",
1472 "created_at": "2013-08-19T17:45:47Z",
1473 "updated_at": "2014-03-03T19:50:11Z",
1474 "pushed_at": "2014-03-03T19:50:09Z",
1475 "git_url": "git://github.com/mreiferson/git-open-pull.git",
1476 "ssh_url": "git@github.com:mreiferson/git-open-pull.git",
1477 "clone_url": "https://github.com/mreiferson/git-open-pull.git",
1478 "svn_url": "https://github.com/mreiferson/git-open-pull",
1479 "homepage": "https://github.com/jehiah/git-open-pull",
1480 "size": 155,
1481 "stargazers_count": 0,
1482 "watchers_count": 0,
1483 "language": "Shell",
1484 "has_issues": false,
1485 "has_downloads": true,
1486 "has_wiki": true,
1487 "forks_count": 0,
1488 "mirror_url": null,
1489 "open_issues_count": 0,
1490 "forks": 0,
1491 "open_issues": 0,
1492 "watchers": 0,
1493 "default_branch": "master"
1494 },
1495 {
1496 "id": 9547968,
1497 "name": "go-hostpool",
1498 "full_name": "mreiferson/go-hostpool",
1499 "owner": {
1500 "login": "mreiferson",
1501 "id": 187441,
1502 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1503 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1504 "url": "https://api.github.com/users/mreiferson",
1505 "html_url": "https://github.com/mreiferson",
1506 "followers_url": "https://api.github.com/users/mreiferson/followers",
1507 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1508 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1509 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1510 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1511 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1512 "repos_url": "https://api.github.com/users/mreiferson/repos",
1513 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1514 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1515 "type": "User",
1516 "site_admin": false
1517 },
1518 "private": false,
1519 "html_url": "https://github.com/mreiferson/go-hostpool",
1520 "description": "Intelligently and flexibly pool among multiple hosts from your Go application",
1521 "fork": true,
1522 "url": "https://api.github.com/repos/mreiferson/go-hostpool",
1523 "forks_url": "https://api.github.com/repos/mreiferson/go-hostpool/forks",
1524 "keys_url": "https://api.github.com/repos/mreiferson/go-hostpool/keys{/key_id}",
1525 "collaborators_url": "https://api.github.com/repos/mreiferson/go-hostpool/collaborators{/collaborator}",
1526 "teams_url": "https://api.github.com/repos/mreiferson/go-hostpool/teams",
1527 "hooks_url": "https://api.github.com/repos/mreiferson/go-hostpool/hooks",
1528 "issue_events_url": "https://api.github.com/repos/mreiferson/go-hostpool/issues/events{/number}",
1529 "events_url": "https://api.github.com/repos/mreiferson/go-hostpool/events",
1530 "assignees_url": "https://api.github.com/repos/mreiferson/go-hostpool/assignees{/user}",
1531 "branches_url": "https://api.github.com/repos/mreiferson/go-hostpool/branches{/branch}",
1532 "tags_url": "https://api.github.com/repos/mreiferson/go-hostpool/tags",
1533 "blobs_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/blobs{/sha}",
1534 "git_tags_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/tags{/sha}",
1535 "git_refs_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/refs{/sha}",
1536 "trees_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/trees{/sha}",
1537 "statuses_url": "https://api.github.com/repos/mreiferson/go-hostpool/statuses/{sha}",
1538 "languages_url": "https://api.github.com/repos/mreiferson/go-hostpool/languages",
1539 "stargazers_url": "https://api.github.com/repos/mreiferson/go-hostpool/stargazers",
1540 "contributors_url": "https://api.github.com/repos/mreiferson/go-hostpool/contributors",
1541 "subscribers_url": "https://api.github.com/repos/mreiferson/go-hostpool/subscribers",
1542 "subscription_url": "https://api.github.com/repos/mreiferson/go-hostpool/subscription",
1543 "commits_url": "https://api.github.com/repos/mreiferson/go-hostpool/commits{/sha}",
1544 "git_commits_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/commits{/sha}",
1545 "comments_url": "https://api.github.com/repos/mreiferson/go-hostpool/comments{/number}",
1546 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-hostpool/issues/comments/{number}",
1547 "contents_url": "https://api.github.com/repos/mreiferson/go-hostpool/contents/{+path}",
1548 "compare_url": "https://api.github.com/repos/mreiferson/go-hostpool/compare/{base}...{head}",
1549 "merges_url": "https://api.github.com/repos/mreiferson/go-hostpool/merges",
1550 "archive_url": "https://api.github.com/repos/mreiferson/go-hostpool/{archive_format}{/ref}",
1551 "downloads_url": "https://api.github.com/repos/mreiferson/go-hostpool/downloads",
1552 "issues_url": "https://api.github.com/repos/mreiferson/go-hostpool/issues{/number}",
1553 "pulls_url": "https://api.github.com/repos/mreiferson/go-hostpool/pulls{/number}",
1554 "milestones_url": "https://api.github.com/repos/mreiferson/go-hostpool/milestones{/number}",
1555 "notifications_url": "https://api.github.com/repos/mreiferson/go-hostpool/notifications{?since,all,participating}",
1556 "labels_url": "https://api.github.com/repos/mreiferson/go-hostpool/labels{/name}",
1557 "releases_url": "https://api.github.com/repos/mreiferson/go-hostpool/releases{/id}",
1558 "created_at": "2013-04-19T15:06:04Z",
1559 "updated_at": "2013-04-30T14:17:45Z",
1560 "pushed_at": "2013-04-30T14:17:44Z",
1561 "git_url": "git://github.com/mreiferson/go-hostpool.git",
1562 "ssh_url": "git@github.com:mreiferson/go-hostpool.git",
1563 "clone_url": "https://github.com/mreiferson/go-hostpool.git",
1564 "svn_url": "https://github.com/mreiferson/go-hostpool",
1565 "homepage": null,
1566 "size": 98,
1567 "stargazers_count": 0,
1568 "watchers_count": 0,
1569 "language": "Go",
1570 "has_issues": false,
1571 "has_downloads": true,
1572 "has_wiki": true,
1573 "forks_count": 0,
1574 "mirror_url": null,
1575 "open_issues_count": 0,
1576 "forks": 0,
1577 "open_issues": 0,
1578 "watchers": 0,
1579 "default_branch": "master"
1580 },
1581 {
1582 "id": 3488675,
1583 "name": "go-httpclient",
1584 "full_name": "mreiferson/go-httpclient",
1585 "owner": {
1586 "login": "mreiferson",
1587 "id": 187441,
1588 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1589 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1590 "url": "https://api.github.com/users/mreiferson",
1591 "html_url": "https://github.com/mreiferson",
1592 "followers_url": "https://api.github.com/users/mreiferson/followers",
1593 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1594 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1595 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1596 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1597 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1598 "repos_url": "https://api.github.com/users/mreiferson/repos",
1599 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1600 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1601 "type": "User",
1602 "site_admin": false
1603 },
1604 "private": false,
1605 "html_url": "https://github.com/mreiferson/go-httpclient",
1606 "description": "a Go HTTP client with timeouts",
1607 "fork": false,
1608 "url": "https://api.github.com/repos/mreiferson/go-httpclient",
1609 "forks_url": "https://api.github.com/repos/mreiferson/go-httpclient/forks",
1610 "keys_url": "https://api.github.com/repos/mreiferson/go-httpclient/keys{/key_id}",
1611 "collaborators_url": "https://api.github.com/repos/mreiferson/go-httpclient/collaborators{/collaborator}",
1612 "teams_url": "https://api.github.com/repos/mreiferson/go-httpclient/teams",
1613 "hooks_url": "https://api.github.com/repos/mreiferson/go-httpclient/hooks",
1614 "issue_events_url": "https://api.github.com/repos/mreiferson/go-httpclient/issues/events{/number}",
1615 "events_url": "https://api.github.com/repos/mreiferson/go-httpclient/events",
1616 "assignees_url": "https://api.github.com/repos/mreiferson/go-httpclient/assignees{/user}",
1617 "branches_url": "https://api.github.com/repos/mreiferson/go-httpclient/branches{/branch}",
1618 "tags_url": "https://api.github.com/repos/mreiferson/go-httpclient/tags",
1619 "blobs_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/blobs{/sha}",
1620 "git_tags_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/tags{/sha}",
1621 "git_refs_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/refs{/sha}",
1622 "trees_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/trees{/sha}",
1623 "statuses_url": "https://api.github.com/repos/mreiferson/go-httpclient/statuses/{sha}",
1624 "languages_url": "https://api.github.com/repos/mreiferson/go-httpclient/languages",
1625 "stargazers_url": "https://api.github.com/repos/mreiferson/go-httpclient/stargazers",
1626 "contributors_url": "https://api.github.com/repos/mreiferson/go-httpclient/contributors",
1627 "subscribers_url": "https://api.github.com/repos/mreiferson/go-httpclient/subscribers",
1628 "subscription_url": "https://api.github.com/repos/mreiferson/go-httpclient/subscription",
1629 "commits_url": "https://api.github.com/repos/mreiferson/go-httpclient/commits{/sha}",
1630 "git_commits_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/commits{/sha}",
1631 "comments_url": "https://api.github.com/repos/mreiferson/go-httpclient/comments{/number}",
1632 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-httpclient/issues/comments/{number}",
1633 "contents_url": "https://api.github.com/repos/mreiferson/go-httpclient/contents/{+path}",
1634 "compare_url": "https://api.github.com/repos/mreiferson/go-httpclient/compare/{base}...{head}",
1635 "merges_url": "https://api.github.com/repos/mreiferson/go-httpclient/merges",
1636 "archive_url": "https://api.github.com/repos/mreiferson/go-httpclient/{archive_format}{/ref}",
1637 "downloads_url": "https://api.github.com/repos/mreiferson/go-httpclient/downloads",
1638 "issues_url": "https://api.github.com/repos/mreiferson/go-httpclient/issues{/number}",
1639 "pulls_url": "https://api.github.com/repos/mreiferson/go-httpclient/pulls{/number}",
1640 "milestones_url": "https://api.github.com/repos/mreiferson/go-httpclient/milestones{/number}",
1641 "notifications_url": "https://api.github.com/repos/mreiferson/go-httpclient/notifications{?since,all,participating}",
1642 "labels_url": "https://api.github.com/repos/mreiferson/go-httpclient/labels{/name}",
1643 "releases_url": "https://api.github.com/repos/mreiferson/go-httpclient/releases{/id}",
1644 "created_at": "2012-02-19T21:51:42Z",
1645 "updated_at": "2014-07-19T16:41:18Z",
1646 "pushed_at": "2014-04-25T16:53:03Z",
1647 "git_url": "git://github.com/mreiferson/go-httpclient.git",
1648 "ssh_url": "git@github.com:mreiferson/go-httpclient.git",
1649 "clone_url": "https://github.com/mreiferson/go-httpclient.git",
1650 "svn_url": "https://github.com/mreiferson/go-httpclient",
1651 "homepage": "",
1652 "size": 362,
1653 "stargazers_count": 167,
1654 "watchers_count": 167,
1655 "language": "Go",
1656 "has_issues": true,
1657 "has_downloads": true,
1658 "has_wiki": false,
1659 "forks_count": 21,
1660 "mirror_url": null,
1661 "open_issues_count": 0,
1662 "forks": 21,
1663 "open_issues": 0,
1664 "watchers": 167,
1665 "default_branch": "master"
1666 },
1667 {
1668 "id": 3924124,
1669 "name": "go-install-as",
1670 "full_name": "mreiferson/go-install-as",
1671 "owner": {
1672 "login": "mreiferson",
1673 "id": 187441,
1674 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1675 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1676 "url": "https://api.github.com/users/mreiferson",
1677 "html_url": "https://github.com/mreiferson",
1678 "followers_url": "https://api.github.com/users/mreiferson/followers",
1679 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1680 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1681 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1682 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1683 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1684 "repos_url": "https://api.github.com/users/mreiferson/repos",
1685 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1686 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1687 "type": "User",
1688 "site_admin": false
1689 },
1690 "private": false,
1691 "html_url": "https://github.com/mreiferson/go-install-as",
1692 "description": "a Go tool to install a package with a specific import path",
1693 "fork": false,
1694 "url": "https://api.github.com/repos/mreiferson/go-install-as",
1695 "forks_url": "https://api.github.com/repos/mreiferson/go-install-as/forks",
1696 "keys_url": "https://api.github.com/repos/mreiferson/go-install-as/keys{/key_id}",
1697 "collaborators_url": "https://api.github.com/repos/mreiferson/go-install-as/collaborators{/collaborator}",
1698 "teams_url": "https://api.github.com/repos/mreiferson/go-install-as/teams",
1699 "hooks_url": "https://api.github.com/repos/mreiferson/go-install-as/hooks",
1700 "issue_events_url": "https://api.github.com/repos/mreiferson/go-install-as/issues/events{/number}",
1701 "events_url": "https://api.github.com/repos/mreiferson/go-install-as/events",
1702 "assignees_url": "https://api.github.com/repos/mreiferson/go-install-as/assignees{/user}",
1703 "branches_url": "https://api.github.com/repos/mreiferson/go-install-as/branches{/branch}",
1704 "tags_url": "https://api.github.com/repos/mreiferson/go-install-as/tags",
1705 "blobs_url": "https://api.github.com/repos/mreiferson/go-install-as/git/blobs{/sha}",
1706 "git_tags_url": "https://api.github.com/repos/mreiferson/go-install-as/git/tags{/sha}",
1707 "git_refs_url": "https://api.github.com/repos/mreiferson/go-install-as/git/refs{/sha}",
1708 "trees_url": "https://api.github.com/repos/mreiferson/go-install-as/git/trees{/sha}",
1709 "statuses_url": "https://api.github.com/repos/mreiferson/go-install-as/statuses/{sha}",
1710 "languages_url": "https://api.github.com/repos/mreiferson/go-install-as/languages",
1711 "stargazers_url": "https://api.github.com/repos/mreiferson/go-install-as/stargazers",
1712 "contributors_url": "https://api.github.com/repos/mreiferson/go-install-as/contributors",
1713 "subscribers_url": "https://api.github.com/repos/mreiferson/go-install-as/subscribers",
1714 "subscription_url": "https://api.github.com/repos/mreiferson/go-install-as/subscription",
1715 "commits_url": "https://api.github.com/repos/mreiferson/go-install-as/commits{/sha}",
1716 "git_commits_url": "https://api.github.com/repos/mreiferson/go-install-as/git/commits{/sha}",
1717 "comments_url": "https://api.github.com/repos/mreiferson/go-install-as/comments{/number}",
1718 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-install-as/issues/comments/{number}",
1719 "contents_url": "https://api.github.com/repos/mreiferson/go-install-as/contents/{+path}",
1720 "compare_url": "https://api.github.com/repos/mreiferson/go-install-as/compare/{base}...{head}",
1721 "merges_url": "https://api.github.com/repos/mreiferson/go-install-as/merges",
1722 "archive_url": "https://api.github.com/repos/mreiferson/go-install-as/{archive_format}{/ref}",
1723 "downloads_url": "https://api.github.com/repos/mreiferson/go-install-as/downloads",
1724 "issues_url": "https://api.github.com/repos/mreiferson/go-install-as/issues{/number}",
1725 "pulls_url": "https://api.github.com/repos/mreiferson/go-install-as/pulls{/number}",
1726 "milestones_url": "https://api.github.com/repos/mreiferson/go-install-as/milestones{/number}",
1727 "notifications_url": "https://api.github.com/repos/mreiferson/go-install-as/notifications{?since,all,participating}",
1728 "labels_url": "https://api.github.com/repos/mreiferson/go-install-as/labels{/name}",
1729 "releases_url": "https://api.github.com/repos/mreiferson/go-install-as/releases{/id}",
1730 "created_at": "2012-04-04T00:17:37Z",
1731 "updated_at": "2014-06-29T20:11:46Z",
1732 "pushed_at": "2012-09-24T16:08:50Z",
1733 "git_url": "git://github.com/mreiferson/go-install-as.git",
1734 "ssh_url": "git@github.com:mreiferson/go-install-as.git",
1735 "clone_url": "https://github.com/mreiferson/go-install-as.git",
1736 "svn_url": "https://github.com/mreiferson/go-install-as",
1737 "homepage": "",
1738 "size": 107,
1739 "stargazers_count": 53,
1740 "watchers_count": 53,
1741 "language": "Shell",
1742 "has_issues": true,
1743 "has_downloads": true,
1744 "has_wiki": false,
1745 "forks_count": 2,
1746 "mirror_url": null,
1747 "open_issues_count": 0,
1748 "forks": 2,
1749 "open_issues": 0,
1750 "watchers": 53,
1751 "default_branch": "master"
1752 },
1753 {
1754 "id": 4744067,
1755 "name": "go-notify",
1756 "full_name": "mreiferson/go-notify",
1757 "owner": {
1758 "login": "mreiferson",
1759 "id": 187441,
1760 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1761 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1762 "url": "https://api.github.com/users/mreiferson",
1763 "html_url": "https://github.com/mreiferson",
1764 "followers_url": "https://api.github.com/users/mreiferson/followers",
1765 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1766 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1767 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1768 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1769 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1770 "repos_url": "https://api.github.com/users/mreiferson/repos",
1771 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1772 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1773 "type": "User",
1774 "site_admin": false
1775 },
1776 "private": false,
1777 "html_url": "https://github.com/mreiferson/go-notify",
1778 "description": "a Go package to observe notable events in a decoupled fashion",
1779 "fork": true,
1780 "url": "https://api.github.com/repos/mreiferson/go-notify",
1781 "forks_url": "https://api.github.com/repos/mreiferson/go-notify/forks",
1782 "keys_url": "https://api.github.com/repos/mreiferson/go-notify/keys{/key_id}",
1783 "collaborators_url": "https://api.github.com/repos/mreiferson/go-notify/collaborators{/collaborator}",
1784 "teams_url": "https://api.github.com/repos/mreiferson/go-notify/teams",
1785 "hooks_url": "https://api.github.com/repos/mreiferson/go-notify/hooks",
1786 "issue_events_url": "https://api.github.com/repos/mreiferson/go-notify/issues/events{/number}",
1787 "events_url": "https://api.github.com/repos/mreiferson/go-notify/events",
1788 "assignees_url": "https://api.github.com/repos/mreiferson/go-notify/assignees{/user}",
1789 "branches_url": "https://api.github.com/repos/mreiferson/go-notify/branches{/branch}",
1790 "tags_url": "https://api.github.com/repos/mreiferson/go-notify/tags",
1791 "blobs_url": "https://api.github.com/repos/mreiferson/go-notify/git/blobs{/sha}",
1792 "git_tags_url": "https://api.github.com/repos/mreiferson/go-notify/git/tags{/sha}",
1793 "git_refs_url": "https://api.github.com/repos/mreiferson/go-notify/git/refs{/sha}",
1794 "trees_url": "https://api.github.com/repos/mreiferson/go-notify/git/trees{/sha}",
1795 "statuses_url": "https://api.github.com/repos/mreiferson/go-notify/statuses/{sha}",
1796 "languages_url": "https://api.github.com/repos/mreiferson/go-notify/languages",
1797 "stargazers_url": "https://api.github.com/repos/mreiferson/go-notify/stargazers",
1798 "contributors_url": "https://api.github.com/repos/mreiferson/go-notify/contributors",
1799 "subscribers_url": "https://api.github.com/repos/mreiferson/go-notify/subscribers",
1800 "subscription_url": "https://api.github.com/repos/mreiferson/go-notify/subscription",
1801 "commits_url": "https://api.github.com/repos/mreiferson/go-notify/commits{/sha}",
1802 "git_commits_url": "https://api.github.com/repos/mreiferson/go-notify/git/commits{/sha}",
1803 "comments_url": "https://api.github.com/repos/mreiferson/go-notify/comments{/number}",
1804 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-notify/issues/comments/{number}",
1805 "contents_url": "https://api.github.com/repos/mreiferson/go-notify/contents/{+path}",
1806 "compare_url": "https://api.github.com/repos/mreiferson/go-notify/compare/{base}...{head}",
1807 "merges_url": "https://api.github.com/repos/mreiferson/go-notify/merges",
1808 "archive_url": "https://api.github.com/repos/mreiferson/go-notify/{archive_format}{/ref}",
1809 "downloads_url": "https://api.github.com/repos/mreiferson/go-notify/downloads",
1810 "issues_url": "https://api.github.com/repos/mreiferson/go-notify/issues{/number}",
1811 "pulls_url": "https://api.github.com/repos/mreiferson/go-notify/pulls{/number}",
1812 "milestones_url": "https://api.github.com/repos/mreiferson/go-notify/milestones{/number}",
1813 "notifications_url": "https://api.github.com/repos/mreiferson/go-notify/notifications{?since,all,participating}",
1814 "labels_url": "https://api.github.com/repos/mreiferson/go-notify/labels{/name}",
1815 "releases_url": "https://api.github.com/repos/mreiferson/go-notify/releases{/id}",
1816 "created_at": "2012-06-21T20:30:43Z",
1817 "updated_at": "2013-01-10T18:07:58Z",
1818 "pushed_at": "2012-06-21T20:30:22Z",
1819 "git_url": "git://github.com/mreiferson/go-notify.git",
1820 "ssh_url": "git@github.com:mreiferson/go-notify.git",
1821 "clone_url": "https://github.com/mreiferson/go-notify.git",
1822 "svn_url": "https://github.com/mreiferson/go-notify",
1823 "homepage": null,
1824 "size": 68,
1825 "stargazers_count": 1,
1826 "watchers_count": 1,
1827 "language": "Go",
1828 "has_issues": false,
1829 "has_downloads": true,
1830 "has_wiki": true,
1831 "forks_count": 0,
1832 "mirror_url": null,
1833 "open_issues_count": 0,
1834 "forks": 0,
1835 "open_issues": 0,
1836 "watchers": 1,
1837 "default_branch": "master"
1838 },
1839 {
1840 "id": 12449360,
1841 "name": "go-nsq",
1842 "full_name": "mreiferson/go-nsq",
1843 "owner": {
1844 "login": "mreiferson",
1845 "id": 187441,
1846 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1847 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1848 "url": "https://api.github.com/users/mreiferson",
1849 "html_url": "https://github.com/mreiferson",
1850 "followers_url": "https://api.github.com/users/mreiferson/followers",
1851 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1852 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1853 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1854 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1855 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1856 "repos_url": "https://api.github.com/users/mreiferson/repos",
1857 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1858 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1859 "type": "User",
1860 "site_admin": false
1861 },
1862 "private": false,
1863 "html_url": "https://github.com/mreiferson/go-nsq",
1864 "description": "the official Go package for NSQ",
1865 "fork": true,
1866 "url": "https://api.github.com/repos/mreiferson/go-nsq",
1867 "forks_url": "https://api.github.com/repos/mreiferson/go-nsq/forks",
1868 "keys_url": "https://api.github.com/repos/mreiferson/go-nsq/keys{/key_id}",
1869 "collaborators_url": "https://api.github.com/repos/mreiferson/go-nsq/collaborators{/collaborator}",
1870 "teams_url": "https://api.github.com/repos/mreiferson/go-nsq/teams",
1871 "hooks_url": "https://api.github.com/repos/mreiferson/go-nsq/hooks",
1872 "issue_events_url": "https://api.github.com/repos/mreiferson/go-nsq/issues/events{/number}",
1873 "events_url": "https://api.github.com/repos/mreiferson/go-nsq/events",
1874 "assignees_url": "https://api.github.com/repos/mreiferson/go-nsq/assignees{/user}",
1875 "branches_url": "https://api.github.com/repos/mreiferson/go-nsq/branches{/branch}",
1876 "tags_url": "https://api.github.com/repos/mreiferson/go-nsq/tags",
1877 "blobs_url": "https://api.github.com/repos/mreiferson/go-nsq/git/blobs{/sha}",
1878 "git_tags_url": "https://api.github.com/repos/mreiferson/go-nsq/git/tags{/sha}",
1879 "git_refs_url": "https://api.github.com/repos/mreiferson/go-nsq/git/refs{/sha}",
1880 "trees_url": "https://api.github.com/repos/mreiferson/go-nsq/git/trees{/sha}",
1881 "statuses_url": "https://api.github.com/repos/mreiferson/go-nsq/statuses/{sha}",
1882 "languages_url": "https://api.github.com/repos/mreiferson/go-nsq/languages",
1883 "stargazers_url": "https://api.github.com/repos/mreiferson/go-nsq/stargazers",
1884 "contributors_url": "https://api.github.com/repos/mreiferson/go-nsq/contributors",
1885 "subscribers_url": "https://api.github.com/repos/mreiferson/go-nsq/subscribers",
1886 "subscription_url": "https://api.github.com/repos/mreiferson/go-nsq/subscription",
1887 "commits_url": "https://api.github.com/repos/mreiferson/go-nsq/commits{/sha}",
1888 "git_commits_url": "https://api.github.com/repos/mreiferson/go-nsq/git/commits{/sha}",
1889 "comments_url": "https://api.github.com/repos/mreiferson/go-nsq/comments{/number}",
1890 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-nsq/issues/comments/{number}",
1891 "contents_url": "https://api.github.com/repos/mreiferson/go-nsq/contents/{+path}",
1892 "compare_url": "https://api.github.com/repos/mreiferson/go-nsq/compare/{base}...{head}",
1893 "merges_url": "https://api.github.com/repos/mreiferson/go-nsq/merges",
1894 "archive_url": "https://api.github.com/repos/mreiferson/go-nsq/{archive_format}{/ref}",
1895 "downloads_url": "https://api.github.com/repos/mreiferson/go-nsq/downloads",
1896 "issues_url": "https://api.github.com/repos/mreiferson/go-nsq/issues{/number}",
1897 "pulls_url": "https://api.github.com/repos/mreiferson/go-nsq/pulls{/number}",
1898 "milestones_url": "https://api.github.com/repos/mreiferson/go-nsq/milestones{/number}",
1899 "notifications_url": "https://api.github.com/repos/mreiferson/go-nsq/notifications{?since,all,participating}",
1900 "labels_url": "https://api.github.com/repos/mreiferson/go-nsq/labels{/name}",
1901 "releases_url": "https://api.github.com/repos/mreiferson/go-nsq/releases{/id}",
1902 "created_at": "2013-08-29T02:07:54Z",
1903 "updated_at": "2014-06-29T13:56:36Z",
1904 "pushed_at": "2014-07-20T16:44:32Z",
1905 "git_url": "git://github.com/mreiferson/go-nsq.git",
1906 "ssh_url": "git@github.com:mreiferson/go-nsq.git",
1907 "clone_url": "https://github.com/mreiferson/go-nsq.git",
1908 "svn_url": "https://github.com/mreiferson/go-nsq",
1909 "homepage": "",
1910 "size": 1783,
1911 "stargazers_count": 0,
1912 "watchers_count": 0,
1913 "language": "Go",
1914 "has_issues": false,
1915 "has_downloads": true,
1916 "has_wiki": false,
1917 "forks_count": 1,
1918 "mirror_url": null,
1919 "open_issues_count": 0,
1920 "forks": 1,
1921 "open_issues": 0,
1922 "watchers": 0,
1923 "default_branch": "master"
1924 },
1925 {
1926 "id": 16654468,
1927 "name": "go-options",
1928 "full_name": "mreiferson/go-options",
1929 "owner": {
1930 "login": "mreiferson",
1931 "id": 187441,
1932 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
1933 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
1934 "url": "https://api.github.com/users/mreiferson",
1935 "html_url": "https://github.com/mreiferson",
1936 "followers_url": "https://api.github.com/users/mreiferson/followers",
1937 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
1938 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
1939 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
1940 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
1941 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
1942 "repos_url": "https://api.github.com/users/mreiferson/repos",
1943 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
1944 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
1945 "type": "User",
1946 "site_admin": false
1947 },
1948 "private": false,
1949 "html_url": "https://github.com/mreiferson/go-options",
1950 "description": "a Go package to structure and resolve options",
1951 "fork": false,
1952 "url": "https://api.github.com/repos/mreiferson/go-options",
1953 "forks_url": "https://api.github.com/repos/mreiferson/go-options/forks",
1954 "keys_url": "https://api.github.com/repos/mreiferson/go-options/keys{/key_id}",
1955 "collaborators_url": "https://api.github.com/repos/mreiferson/go-options/collaborators{/collaborator}",
1956 "teams_url": "https://api.github.com/repos/mreiferson/go-options/teams",
1957 "hooks_url": "https://api.github.com/repos/mreiferson/go-options/hooks",
1958 "issue_events_url": "https://api.github.com/repos/mreiferson/go-options/issues/events{/number}",
1959 "events_url": "https://api.github.com/repos/mreiferson/go-options/events",
1960 "assignees_url": "https://api.github.com/repos/mreiferson/go-options/assignees{/user}",
1961 "branches_url": "https://api.github.com/repos/mreiferson/go-options/branches{/branch}",
1962 "tags_url": "https://api.github.com/repos/mreiferson/go-options/tags",
1963 "blobs_url": "https://api.github.com/repos/mreiferson/go-options/git/blobs{/sha}",
1964 "git_tags_url": "https://api.github.com/repos/mreiferson/go-options/git/tags{/sha}",
1965 "git_refs_url": "https://api.github.com/repos/mreiferson/go-options/git/refs{/sha}",
1966 "trees_url": "https://api.github.com/repos/mreiferson/go-options/git/trees{/sha}",
1967 "statuses_url": "https://api.github.com/repos/mreiferson/go-options/statuses/{sha}",
1968 "languages_url": "https://api.github.com/repos/mreiferson/go-options/languages",
1969 "stargazers_url": "https://api.github.com/repos/mreiferson/go-options/stargazers",
1970 "contributors_url": "https://api.github.com/repos/mreiferson/go-options/contributors",
1971 "subscribers_url": "https://api.github.com/repos/mreiferson/go-options/subscribers",
1972 "subscription_url": "https://api.github.com/repos/mreiferson/go-options/subscription",
1973 "commits_url": "https://api.github.com/repos/mreiferson/go-options/commits{/sha}",
1974 "git_commits_url": "https://api.github.com/repos/mreiferson/go-options/git/commits{/sha}",
1975 "comments_url": "https://api.github.com/repos/mreiferson/go-options/comments{/number}",
1976 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-options/issues/comments/{number}",
1977 "contents_url": "https://api.github.com/repos/mreiferson/go-options/contents/{+path}",
1978 "compare_url": "https://api.github.com/repos/mreiferson/go-options/compare/{base}...{head}",
1979 "merges_url": "https://api.github.com/repos/mreiferson/go-options/merges",
1980 "archive_url": "https://api.github.com/repos/mreiferson/go-options/{archive_format}{/ref}",
1981 "downloads_url": "https://api.github.com/repos/mreiferson/go-options/downloads",
1982 "issues_url": "https://api.github.com/repos/mreiferson/go-options/issues{/number}",
1983 "pulls_url": "https://api.github.com/repos/mreiferson/go-options/pulls{/number}",
1984 "milestones_url": "https://api.github.com/repos/mreiferson/go-options/milestones{/number}",
1985 "notifications_url": "https://api.github.com/repos/mreiferson/go-options/notifications{?since,all,participating}",
1986 "labels_url": "https://api.github.com/repos/mreiferson/go-options/labels{/name}",
1987 "releases_url": "https://api.github.com/repos/mreiferson/go-options/releases{/id}",
1988 "created_at": "2014-02-08T22:19:33Z",
1989 "updated_at": "2014-02-16T00:39:59Z",
1990 "pushed_at": "2014-02-16T00:39:58Z",
1991 "git_url": "git://github.com/mreiferson/go-options.git",
1992 "ssh_url": "git@github.com:mreiferson/go-options.git",
1993 "clone_url": "https://github.com/mreiferson/go-options.git",
1994 "svn_url": "https://github.com/mreiferson/go-options",
1995 "homepage": null,
1996 "size": 128,
1997 "stargazers_count": 1,
1998 "watchers_count": 1,
1999 "language": "Go",
2000 "has_issues": true,
2001 "has_downloads": true,
2002 "has_wiki": true,
2003 "forks_count": 0,
2004 "mirror_url": null,
2005 "open_issues_count": 0,
2006 "forks": 0,
2007 "open_issues": 0,
2008 "watchers": 1,
2009 "default_branch": "master"
2010 },
2011 {
2012 "id": 3924909,
2013 "name": "go-simplejson",
2014 "full_name": "mreiferson/go-simplejson",
2015 "owner": {
2016 "login": "mreiferson",
2017 "id": 187441,
2018 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
2019 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
2020 "url": "https://api.github.com/users/mreiferson",
2021 "html_url": "https://github.com/mreiferson",
2022 "followers_url": "https://api.github.com/users/mreiferson/followers",
2023 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
2024 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
2025 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
2026 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
2027 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
2028 "repos_url": "https://api.github.com/users/mreiferson/repos",
2029 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
2030 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
2031 "type": "User",
2032 "site_admin": false
2033 },
2034 "private": false,
2035 "html_url": "https://github.com/mreiferson/go-simplejson",
2036 "description": "a Go package to interact with arbitrary JSON",
2037 "fork": true,
2038 "url": "https://api.github.com/repos/mreiferson/go-simplejson",
2039 "forks_url": "https://api.github.com/repos/mreiferson/go-simplejson/forks",
2040 "keys_url": "https://api.github.com/repos/mreiferson/go-simplejson/keys{/key_id}",
2041 "collaborators_url": "https://api.github.com/repos/mreiferson/go-simplejson/collaborators{/collaborator}",
2042 "teams_url": "https://api.github.com/repos/mreiferson/go-simplejson/teams",
2043 "hooks_url": "https://api.github.com/repos/mreiferson/go-simplejson/hooks",
2044 "issue_events_url": "https://api.github.com/repos/mreiferson/go-simplejson/issues/events{/number}",
2045 "events_url": "https://api.github.com/repos/mreiferson/go-simplejson/events",
2046 "assignees_url": "https://api.github.com/repos/mreiferson/go-simplejson/assignees{/user}",
2047 "branches_url": "https://api.github.com/repos/mreiferson/go-simplejson/branches{/branch}",
2048 "tags_url": "https://api.github.com/repos/mreiferson/go-simplejson/tags",
2049 "blobs_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/blobs{/sha}",
2050 "git_tags_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/tags{/sha}",
2051 "git_refs_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/refs{/sha}",
2052 "trees_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/trees{/sha}",
2053 "statuses_url": "https://api.github.com/repos/mreiferson/go-simplejson/statuses/{sha}",
2054 "languages_url": "https://api.github.com/repos/mreiferson/go-simplejson/languages",
2055 "stargazers_url": "https://api.github.com/repos/mreiferson/go-simplejson/stargazers",
2056 "contributors_url": "https://api.github.com/repos/mreiferson/go-simplejson/contributors",
2057 "subscribers_url": "https://api.github.com/repos/mreiferson/go-simplejson/subscribers",
2058 "subscription_url": "https://api.github.com/repos/mreiferson/go-simplejson/subscription",
2059 "commits_url": "https://api.github.com/repos/mreiferson/go-simplejson/commits{/sha}",
2060 "git_commits_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/commits{/sha}",
2061 "comments_url": "https://api.github.com/repos/mreiferson/go-simplejson/comments{/number}",
2062 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-simplejson/issues/comments/{number}",
2063 "contents_url": "https://api.github.com/repos/mreiferson/go-simplejson/contents/{+path}",
2064 "compare_url": "https://api.github.com/repos/mreiferson/go-simplejson/compare/{base}...{head}",
2065 "merges_url": "https://api.github.com/repos/mreiferson/go-simplejson/merges",
2066 "archive_url": "https://api.github.com/repos/mreiferson/go-simplejson/{archive_format}{/ref}",
2067 "downloads_url": "https://api.github.com/repos/mreiferson/go-simplejson/downloads",
2068 "issues_url": "https://api.github.com/repos/mreiferson/go-simplejson/issues{/number}",
2069 "pulls_url": "https://api.github.com/repos/mreiferson/go-simplejson/pulls{/number}",
2070 "milestones_url": "https://api.github.com/repos/mreiferson/go-simplejson/milestones{/number}",
2071 "notifications_url": "https://api.github.com/repos/mreiferson/go-simplejson/notifications{?since,all,participating}",
2072 "labels_url": "https://api.github.com/repos/mreiferson/go-simplejson/labels{/name}",
2073 "releases_url": "https://api.github.com/repos/mreiferson/go-simplejson/releases{/id}",
2074 "created_at": "2012-04-04T02:36:33Z",
2075 "updated_at": "2014-06-25T01:24:01Z",
2076 "pushed_at": "2014-06-30T15:13:50Z",
2077 "git_url": "git://github.com/mreiferson/go-simplejson.git",
2078 "ssh_url": "git@github.com:mreiferson/go-simplejson.git",
2079 "clone_url": "https://github.com/mreiferson/go-simplejson.git",
2080 "svn_url": "https://github.com/mreiferson/go-simplejson",
2081 "homepage": "",
2082 "size": 210,
2083 "stargazers_count": 1,
2084 "watchers_count": 1,
2085 "language": "Go",
2086 "has_issues": false,
2087 "has_downloads": true,
2088 "has_wiki": false,
2089 "forks_count": 0,
2090 "mirror_url": null,
2091 "open_issues_count": 0,
2092 "forks": 0,
2093 "open_issues": 0,
2094 "watchers": 1,
2095 "default_branch": "master"
2096 },
2097 {
2098 "id": 8614089,
2099 "name": "go-simplelog",
2100 "full_name": "mreiferson/go-simplelog",
2101 "owner": {
2102 "login": "mreiferson",
2103 "id": 187441,
2104 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
2105 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
2106 "url": "https://api.github.com/users/mreiferson",
2107 "html_url": "https://github.com/mreiferson",
2108 "followers_url": "https://api.github.com/users/mreiferson/followers",
2109 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
2110 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
2111 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
2112 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
2113 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
2114 "repos_url": "https://api.github.com/users/mreiferson/repos",
2115 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
2116 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
2117 "type": "User",
2118 "site_admin": false
2119 },
2120 "private": false,
2121 "html_url": "https://github.com/mreiferson/go-simplelog",
2122 "description": "a simple logging package for Go (inspired by Tornado)",
2123 "fork": false,
2124 "url": "https://api.github.com/repos/mreiferson/go-simplelog",
2125 "forks_url": "https://api.github.com/repos/mreiferson/go-simplelog/forks",
2126 "keys_url": "https://api.github.com/repos/mreiferson/go-simplelog/keys{/key_id}",
2127 "collaborators_url": "https://api.github.com/repos/mreiferson/go-simplelog/collaborators{/collaborator}",
2128 "teams_url": "https://api.github.com/repos/mreiferson/go-simplelog/teams",
2129 "hooks_url": "https://api.github.com/repos/mreiferson/go-simplelog/hooks",
2130 "issue_events_url": "https://api.github.com/repos/mreiferson/go-simplelog/issues/events{/number}",
2131 "events_url": "https://api.github.com/repos/mreiferson/go-simplelog/events",
2132 "assignees_url": "https://api.github.com/repos/mreiferson/go-simplelog/assignees{/user}",
2133 "branches_url": "https://api.github.com/repos/mreiferson/go-simplelog/branches{/branch}",
2134 "tags_url": "https://api.github.com/repos/mreiferson/go-simplelog/tags",
2135 "blobs_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/blobs{/sha}",
2136 "git_tags_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/tags{/sha}",
2137 "git_refs_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/refs{/sha}",
2138 "trees_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/trees{/sha}",
2139 "statuses_url": "https://api.github.com/repos/mreiferson/go-simplelog/statuses/{sha}",
2140 "languages_url": "https://api.github.com/repos/mreiferson/go-simplelog/languages",
2141 "stargazers_url": "https://api.github.com/repos/mreiferson/go-simplelog/stargazers",
2142 "contributors_url": "https://api.github.com/repos/mreiferson/go-simplelog/contributors",
2143 "subscribers_url": "https://api.github.com/repos/mreiferson/go-simplelog/subscribers",
2144 "subscription_url": "https://api.github.com/repos/mreiferson/go-simplelog/subscription",
2145 "commits_url": "https://api.github.com/repos/mreiferson/go-simplelog/commits{/sha}",
2146 "git_commits_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/commits{/sha}",
2147 "comments_url": "https://api.github.com/repos/mreiferson/go-simplelog/comments{/number}",
2148 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-simplelog/issues/comments/{number}",
2149 "contents_url": "https://api.github.com/repos/mreiferson/go-simplelog/contents/{+path}",
2150 "compare_url": "https://api.github.com/repos/mreiferson/go-simplelog/compare/{base}...{head}",
2151 "merges_url": "https://api.github.com/repos/mreiferson/go-simplelog/merges",
2152 "archive_url": "https://api.github.com/repos/mreiferson/go-simplelog/{archive_format}{/ref}",
2153 "downloads_url": "https://api.github.com/repos/mreiferson/go-simplelog/downloads",
2154 "issues_url": "https://api.github.com/repos/mreiferson/go-simplelog/issues{/number}",
2155 "pulls_url": "https://api.github.com/repos/mreiferson/go-simplelog/pulls{/number}",
2156 "milestones_url": "https://api.github.com/repos/mreiferson/go-simplelog/milestones{/number}",
2157 "notifications_url": "https://api.github.com/repos/mreiferson/go-simplelog/notifications{?since,all,participating}",
2158 "labels_url": "https://api.github.com/repos/mreiferson/go-simplelog/labels{/name}",
2159 "releases_url": "https://api.github.com/repos/mreiferson/go-simplelog/releases{/id}",
2160 "created_at": "2013-03-06T21:53:48Z",
2161 "updated_at": "2013-10-11T22:49:05Z",
2162 "pushed_at": "2013-03-31T23:20:11Z",
2163 "git_url": "git://github.com/mreiferson/go-simplelog.git",
2164 "ssh_url": "git@github.com:mreiferson/go-simplelog.git",
2165 "clone_url": "https://github.com/mreiferson/go-simplelog.git",
2166 "svn_url": "https://github.com/mreiferson/go-simplelog",
2167 "homepage": null,
2168 "size": 140,
2169 "stargazers_count": 1,
2170 "watchers_count": 1,
2171 "language": "Go",
2172 "has_issues": true,
2173 "has_downloads": true,
2174 "has_wiki": false,
2175 "forks_count": 1,
2176 "mirror_url": null,
2177 "open_issues_count": 0,
2178 "forks": 1,
2179 "open_issues": 0,
2180 "watchers": 1,
2181 "default_branch": "master"
2182 },
2183 {
2184 "id": 12498288,
2185 "name": "go-snappystream",
2186 "full_name": "mreiferson/go-snappystream",
2187 "owner": {
2188 "login": "mreiferson",
2189 "id": 187441,
2190 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
2191 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
2192 "url": "https://api.github.com/users/mreiferson",
2193 "html_url": "https://github.com/mreiferson",
2194 "followers_url": "https://api.github.com/users/mreiferson/followers",
2195 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
2196 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
2197 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
2198 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
2199 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
2200 "repos_url": "https://api.github.com/users/mreiferson/repos",
2201 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
2202 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
2203 "type": "User",
2204 "site_admin": false
2205 },
2206 "private": false,
2207 "html_url": "https://github.com/mreiferson/go-snappystream",
2208 "description": "a Go package for framed snappy streams",
2209 "fork": false,
2210 "url": "https://api.github.com/repos/mreiferson/go-snappystream",
2211 "forks_url": "https://api.github.com/repos/mreiferson/go-snappystream/forks",
2212 "keys_url": "https://api.github.com/repos/mreiferson/go-snappystream/keys{/key_id}",
2213 "collaborators_url": "https://api.github.com/repos/mreiferson/go-snappystream/collaborators{/collaborator}",
2214 "teams_url": "https://api.github.com/repos/mreiferson/go-snappystream/teams",
2215 "hooks_url": "https://api.github.com/repos/mreiferson/go-snappystream/hooks",
2216 "issue_events_url": "https://api.github.com/repos/mreiferson/go-snappystream/issues/events{/number}",
2217 "events_url": "https://api.github.com/repos/mreiferson/go-snappystream/events",
2218 "assignees_url": "https://api.github.com/repos/mreiferson/go-snappystream/assignees{/user}",
2219 "branches_url": "https://api.github.com/repos/mreiferson/go-snappystream/branches{/branch}",
2220 "tags_url": "https://api.github.com/repos/mreiferson/go-snappystream/tags",
2221 "blobs_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/blobs{/sha}",
2222 "git_tags_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/tags{/sha}",
2223 "git_refs_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/refs{/sha}",
2224 "trees_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/trees{/sha}",
2225 "statuses_url": "https://api.github.com/repos/mreiferson/go-snappystream/statuses/{sha}",
2226 "languages_url": "https://api.github.com/repos/mreiferson/go-snappystream/languages",
2227 "stargazers_url": "https://api.github.com/repos/mreiferson/go-snappystream/stargazers",
2228 "contributors_url": "https://api.github.com/repos/mreiferson/go-snappystream/contributors",
2229 "subscribers_url": "https://api.github.com/repos/mreiferson/go-snappystream/subscribers",
2230 "subscription_url": "https://api.github.com/repos/mreiferson/go-snappystream/subscription",
2231 "commits_url": "https://api.github.com/repos/mreiferson/go-snappystream/commits{/sha}",
2232 "git_commits_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/commits{/sha}",
2233 "comments_url": "https://api.github.com/repos/mreiferson/go-snappystream/comments{/number}",
2234 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-snappystream/issues/comments/{number}",
2235 "contents_url": "https://api.github.com/repos/mreiferson/go-snappystream/contents/{+path}",
2236 "compare_url": "https://api.github.com/repos/mreiferson/go-snappystream/compare/{base}...{head}",
2237 "merges_url": "https://api.github.com/repos/mreiferson/go-snappystream/merges",
2238 "archive_url": "https://api.github.com/repos/mreiferson/go-snappystream/{archive_format}{/ref}",
2239 "downloads_url": "https://api.github.com/repos/mreiferson/go-snappystream/downloads",
2240 "issues_url": "https://api.github.com/repos/mreiferson/go-snappystream/issues{/number}",
2241 "pulls_url": "https://api.github.com/repos/mreiferson/go-snappystream/pulls{/number}",
2242 "milestones_url": "https://api.github.com/repos/mreiferson/go-snappystream/milestones{/number}",
2243 "notifications_url": "https://api.github.com/repos/mreiferson/go-snappystream/notifications{?since,all,participating}",
2244 "labels_url": "https://api.github.com/repos/mreiferson/go-snappystream/labels{/name}",
2245 "releases_url": "https://api.github.com/repos/mreiferson/go-snappystream/releases{/id}",
2246 "created_at": "2013-08-31T00:41:11Z",
2247 "updated_at": "2014-07-20T07:52:45Z",
2248 "pushed_at": "2013-09-17T21:00:14Z",
2249 "git_url": "git://github.com/mreiferson/go-snappystream.git",
2250 "ssh_url": "git@github.com:mreiferson/go-snappystream.git",
2251 "clone_url": "https://github.com/mreiferson/go-snappystream.git",
2252 "svn_url": "https://github.com/mreiferson/go-snappystream",
2253 "homepage": null,
2254 "size": 184,
2255 "stargazers_count": 21,
2256 "watchers_count": 21,
2257 "language": "Go",
2258 "has_issues": true,
2259 "has_downloads": true,
2260 "has_wiki": true,
2261 "forks_count": 1,
2262 "mirror_url": null,
2263 "open_issues_count": 0,
2264 "forks": 1,
2265 "open_issues": 0,
2266 "watchers": 21,
2267 "default_branch": "master"
2268 },
2269 {
2270 "id": 5183238,
2271 "name": "go-stat",
2272 "full_name": "mreiferson/go-stat",
2273 "owner": {
2274 "login": "mreiferson",
2275 "id": 187441,
2276 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
2277 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
2278 "url": "https://api.github.com/users/mreiferson",
2279 "html_url": "https://github.com/mreiferson",
2280 "followers_url": "https://api.github.com/users/mreiferson/followers",
2281 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
2282 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
2283 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
2284 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
2285 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
2286 "repos_url": "https://api.github.com/users/mreiferson/repos",
2287 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
2288 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
2289 "type": "User",
2290 "site_admin": false
2291 },
2292 "private": false,
2293 "html_url": "https://github.com/mreiferson/go-stat",
2294 "description": "performant instrumentation/profiling for Go",
2295 "fork": false,
2296 "url": "https://api.github.com/repos/mreiferson/go-stat",
2297 "forks_url": "https://api.github.com/repos/mreiferson/go-stat/forks",
2298 "keys_url": "https://api.github.com/repos/mreiferson/go-stat/keys{/key_id}",
2299 "collaborators_url": "https://api.github.com/repos/mreiferson/go-stat/collaborators{/collaborator}",
2300 "teams_url": "https://api.github.com/repos/mreiferson/go-stat/teams",
2301 "hooks_url": "https://api.github.com/repos/mreiferson/go-stat/hooks",
2302 "issue_events_url": "https://api.github.com/repos/mreiferson/go-stat/issues/events{/number}",
2303 "events_url": "https://api.github.com/repos/mreiferson/go-stat/events",
2304 "assignees_url": "https://api.github.com/repos/mreiferson/go-stat/assignees{/user}",
2305 "branches_url": "https://api.github.com/repos/mreiferson/go-stat/branches{/branch}",
2306 "tags_url": "https://api.github.com/repos/mreiferson/go-stat/tags",
2307 "blobs_url": "https://api.github.com/repos/mreiferson/go-stat/git/blobs{/sha}",
2308 "git_tags_url": "https://api.github.com/repos/mreiferson/go-stat/git/tags{/sha}",
2309 "git_refs_url": "https://api.github.com/repos/mreiferson/go-stat/git/refs{/sha}",
2310 "trees_url": "https://api.github.com/repos/mreiferson/go-stat/git/trees{/sha}",
2311 "statuses_url": "https://api.github.com/repos/mreiferson/go-stat/statuses/{sha}",
2312 "languages_url": "https://api.github.com/repos/mreiferson/go-stat/languages",
2313 "stargazers_url": "https://api.github.com/repos/mreiferson/go-stat/stargazers",
2314 "contributors_url": "https://api.github.com/repos/mreiferson/go-stat/contributors",
2315 "subscribers_url": "https://api.github.com/repos/mreiferson/go-stat/subscribers",
2316 "subscription_url": "https://api.github.com/repos/mreiferson/go-stat/subscription",
2317 "commits_url": "https://api.github.com/repos/mreiferson/go-stat/commits{/sha}",
2318 "git_commits_url": "https://api.github.com/repos/mreiferson/go-stat/git/commits{/sha}",
2319 "comments_url": "https://api.github.com/repos/mreiferson/go-stat/comments{/number}",
2320 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-stat/issues/comments/{number}",
2321 "contents_url": "https://api.github.com/repos/mreiferson/go-stat/contents/{+path}",
2322 "compare_url": "https://api.github.com/repos/mreiferson/go-stat/compare/{base}...{head}",
2323 "merges_url": "https://api.github.com/repos/mreiferson/go-stat/merges",
2324 "archive_url": "https://api.github.com/repos/mreiferson/go-stat/{archive_format}{/ref}",
2325 "downloads_url": "https://api.github.com/repos/mreiferson/go-stat/downloads",
2326 "issues_url": "https://api.github.com/repos/mreiferson/go-stat/issues{/number}",
2327 "pulls_url": "https://api.github.com/repos/mreiferson/go-stat/pulls{/number}",
2328 "milestones_url": "https://api.github.com/repos/mreiferson/go-stat/milestones{/number}",
2329 "notifications_url": "https://api.github.com/repos/mreiferson/go-stat/notifications{?since,all,participating}",
2330 "labels_url": "https://api.github.com/repos/mreiferson/go-stat/labels{/name}",
2331 "releases_url": "https://api.github.com/repos/mreiferson/go-stat/releases{/id}",
2332 "created_at": "2012-07-25T19:03:42Z",
2333 "updated_at": "2014-01-10T04:39:14Z",
2334 "pushed_at": "2012-07-25T19:04:37Z",
2335 "git_url": "git://github.com/mreiferson/go-stat.git",
2336 "ssh_url": "git@github.com:mreiferson/go-stat.git",
2337 "clone_url": "https://github.com/mreiferson/go-stat.git",
2338 "svn_url": "https://github.com/mreiferson/go-stat",
2339 "homepage": null,
2340 "size": 96,
2341 "stargazers_count": 1,
2342 "watchers_count": 1,
2343 "language": "Go",
2344 "has_issues": true,
2345 "has_downloads": true,
2346 "has_wiki": true,
2347 "forks_count": 0,
2348 "mirror_url": null,
2349 "open_issues_count": 0,
2350 "forks": 0,
2351 "open_issues": 0,
2352 "watchers": 1,
2353 "default_branch": "master"
2354 },
2355 {
2356 "id": 8662365,
2357 "name": "go-ujson",
2358 "full_name": "mreiferson/go-ujson",
2359 "owner": {
2360 "login": "mreiferson",
2361 "id": 187441,
2362 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
2363 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
2364 "url": "https://api.github.com/users/mreiferson",
2365 "html_url": "https://github.com/mreiferson",
2366 "followers_url": "https://api.github.com/users/mreiferson/followers",
2367 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
2368 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
2369 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
2370 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
2371 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
2372 "repos_url": "https://api.github.com/users/mreiferson/repos",
2373 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
2374 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
2375 "type": "User",
2376 "site_admin": false
2377 },
2378 "private": false,
2379 "html_url": "https://github.com/mreiferson/go-ujson",
2380 "description": "a pure Go port of ultrajson",
2381 "fork": false,
2382 "url": "https://api.github.com/repos/mreiferson/go-ujson",
2383 "forks_url": "https://api.github.com/repos/mreiferson/go-ujson/forks",
2384 "keys_url": "https://api.github.com/repos/mreiferson/go-ujson/keys{/key_id}",
2385 "collaborators_url": "https://api.github.com/repos/mreiferson/go-ujson/collaborators{/collaborator}",
2386 "teams_url": "https://api.github.com/repos/mreiferson/go-ujson/teams",
2387 "hooks_url": "https://api.github.com/repos/mreiferson/go-ujson/hooks",
2388 "issue_events_url": "https://api.github.com/repos/mreiferson/go-ujson/issues/events{/number}",
2389 "events_url": "https://api.github.com/repos/mreiferson/go-ujson/events",
2390 "assignees_url": "https://api.github.com/repos/mreiferson/go-ujson/assignees{/user}",
2391 "branches_url": "https://api.github.com/repos/mreiferson/go-ujson/branches{/branch}",
2392 "tags_url": "https://api.github.com/repos/mreiferson/go-ujson/tags",
2393 "blobs_url": "https://api.github.com/repos/mreiferson/go-ujson/git/blobs{/sha}",
2394 "git_tags_url": "https://api.github.com/repos/mreiferson/go-ujson/git/tags{/sha}",
2395 "git_refs_url": "https://api.github.com/repos/mreiferson/go-ujson/git/refs{/sha}",
2396 "trees_url": "https://api.github.com/repos/mreiferson/go-ujson/git/trees{/sha}",
2397 "statuses_url": "https://api.github.com/repos/mreiferson/go-ujson/statuses/{sha}",
2398 "languages_url": "https://api.github.com/repos/mreiferson/go-ujson/languages",
2399 "stargazers_url": "https://api.github.com/repos/mreiferson/go-ujson/stargazers",
2400 "contributors_url": "https://api.github.com/repos/mreiferson/go-ujson/contributors",
2401 "subscribers_url": "https://api.github.com/repos/mreiferson/go-ujson/subscribers",
2402 "subscription_url": "https://api.github.com/repos/mreiferson/go-ujson/subscription",
2403 "commits_url": "https://api.github.com/repos/mreiferson/go-ujson/commits{/sha}",
2404 "git_commits_url": "https://api.github.com/repos/mreiferson/go-ujson/git/commits{/sha}",
2405 "comments_url": "https://api.github.com/repos/mreiferson/go-ujson/comments{/number}",
2406 "issue_comment_url": "https://api.github.com/repos/mreiferson/go-ujson/issues/comments/{number}",
2407 "contents_url": "https://api.github.com/repos/mreiferson/go-ujson/contents/{+path}",
2408 "compare_url": "https://api.github.com/repos/mreiferson/go-ujson/compare/{base}...{head}",
2409 "merges_url": "https://api.github.com/repos/mreiferson/go-ujson/merges",
2410 "archive_url": "https://api.github.com/repos/mreiferson/go-ujson/{archive_format}{/ref}",
2411 "downloads_url": "https://api.github.com/repos/mreiferson/go-ujson/downloads",
2412 "issues_url": "https://api.github.com/repos/mreiferson/go-ujson/issues{/number}",
2413 "pulls_url": "https://api.github.com/repos/mreiferson/go-ujson/pulls{/number}",
2414 "milestones_url": "https://api.github.com/repos/mreiferson/go-ujson/milestones{/number}",
2415 "notifications_url": "https://api.github.com/repos/mreiferson/go-ujson/notifications{?since,all,participating}",
2416 "labels_url": "https://api.github.com/repos/mreiferson/go-ujson/labels{/name}",
2417 "releases_url": "https://api.github.com/repos/mreiferson/go-ujson/releases{/id}",
2418 "created_at": "2013-03-08T23:57:54Z",
2419 "updated_at": "2014-06-26T01:50:40Z",
2420 "pushed_at": "2013-11-10T19:49:16Z",
2421 "git_url": "git://github.com/mreiferson/go-ujson.git",
2422 "ssh_url": "git@github.com:mreiferson/go-ujson.git",
2423 "clone_url": "https://github.com/mreiferson/go-ujson.git",
2424 "svn_url": "https://github.com/mreiferson/go-ujson",
2425 "homepage": "",
2426 "size": 140,
2427 "stargazers_count": 31,
2428 "watchers_count": 31,
2429 "language": "Go",
2430 "has_issues": true,
2431 "has_downloads": true,
2432 "has_wiki": true,
2433 "forks_count": 8,
2434 "mirror_url": null,
2435 "open_issues_count": 0,
2436 "forks": 8,
2437 "open_issues": 0,
2438 "watchers": 31,
2439 "default_branch": "master"
2440 },
2441 {
2442 "id": 12815437,
2443 "name": "godep",
2444 "full_name": "mreiferson/godep",
2445 "owner": {
2446 "login": "mreiferson",
2447 "id": 187441,
2448 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
2449 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
2450 "url": "https://api.github.com/users/mreiferson",
2451 "html_url": "https://github.com/mreiferson",
2452 "followers_url": "https://api.github.com/users/mreiferson/followers",
2453 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
2454 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
2455 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
2456 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
2457 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
2458 "repos_url": "https://api.github.com/users/mreiferson/repos",
2459 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
2460 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
2461 "type": "User",
2462 "site_admin": false
2463 },
2464 "private": false,
2465 "html_url": "https://github.com/mreiferson/godep",
2466 "description": "dependency tool for go",
2467 "fork": true,
2468 "url": "https://api.github.com/repos/mreiferson/godep",
2469 "forks_url": "https://api.github.com/repos/mreiferson/godep/forks",
2470 "keys_url": "https://api.github.com/repos/mreiferson/godep/keys{/key_id}",
2471 "collaborators_url": "https://api.github.com/repos/mreiferson/godep/collaborators{/collaborator}",
2472 "teams_url": "https://api.github.com/repos/mreiferson/godep/teams",
2473 "hooks_url": "https://api.github.com/repos/mreiferson/godep/hooks",
2474 "issue_events_url": "https://api.github.com/repos/mreiferson/godep/issues/events{/number}",
2475 "events_url": "https://api.github.com/repos/mreiferson/godep/events",
2476 "assignees_url": "https://api.github.com/repos/mreiferson/godep/assignees{/user}",
2477 "branches_url": "https://api.github.com/repos/mreiferson/godep/branches{/branch}",
2478 "tags_url": "https://api.github.com/repos/mreiferson/godep/tags",
2479 "blobs_url": "https://api.github.com/repos/mreiferson/godep/git/blobs{/sha}",
2480 "git_tags_url": "https://api.github.com/repos/mreiferson/godep/git/tags{/sha}",
2481 "git_refs_url": "https://api.github.com/repos/mreiferson/godep/git/refs{/sha}",
2482 "trees_url": "https://api.github.com/repos/mreiferson/godep/git/trees{/sha}",
2483 "statuses_url": "https://api.github.com/repos/mreiferson/godep/statuses/{sha}",
2484 "languages_url": "https://api.github.com/repos/mreiferson/godep/languages",
2485 "stargazers_url": "https://api.github.com/repos/mreiferson/godep/stargazers",
2486 "contributors_url": "https://api.github.com/repos/mreiferson/godep/contributors",
2487 "subscribers_url": "https://api.github.com/repos/mreiferson/godep/subscribers",
2488 "subscription_url": "https://api.github.com/repos/mreiferson/godep/subscription",
2489 "commits_url": "https://api.github.com/repos/mreiferson/godep/commits{/sha}",
2490 "git_commits_url": "https://api.github.com/repos/mreiferson/godep/git/commits{/sha}",
2491 "comments_url": "https://api.github.com/repos/mreiferson/godep/comments{/number}",
2492 "issue_comment_url": "https://api.github.com/repos/mreiferson/godep/issues/comments/{number}",
2493 "contents_url": "https://api.github.com/repos/mreiferson/godep/contents/{+path}",
2494 "compare_url": "https://api.github.com/repos/mreiferson/godep/compare/{base}...{head}",
2495 "merges_url": "https://api.github.com/repos/mreiferson/godep/merges",
2496 "archive_url": "https://api.github.com/repos/mreiferson/godep/{archive_format}{/ref}",
2497 "downloads_url": "https://api.github.com/repos/mreiferson/godep/downloads",
2498 "issues_url": "https://api.github.com/repos/mreiferson/godep/issues{/number}",
2499 "pulls_url": "https://api.github.com/repos/mreiferson/godep/pulls{/number}",
2500 "milestones_url": "https://api.github.com/repos/mreiferson/godep/milestones{/number}",
2501 "notifications_url": "https://api.github.com/repos/mreiferson/godep/notifications{?since,all,participating}",
2502 "labels_url": "https://api.github.com/repos/mreiferson/godep/labels{/name}",
2503 "releases_url": "https://api.github.com/repos/mreiferson/godep/releases{/id}",
2504 "created_at": "2013-09-13T17:36:10Z",
2505 "updated_at": "2014-03-21T02:53:20Z",
2506 "pushed_at": "2014-01-05T18:07:02Z",
2507 "git_url": "git://github.com/mreiferson/godep.git",
2508 "ssh_url": "git@github.com:mreiferson/godep.git",
2509 "clone_url": "https://github.com/mreiferson/godep.git",
2510 "svn_url": "https://github.com/mreiferson/godep",
2511 "homepage": "http://godoc.org/github.com/kr/godep",
2512 "size": 196,
2513 "stargazers_count": 0,
2514 "watchers_count": 0,
2515 "language": "Go",
2516 "has_issues": false,
2517 "has_downloads": true,
2518 "has_wiki": true,
2519 "forks_count": 0,
2520 "mirror_url": null,
2521 "open_issues_count": 0,
2522 "forks": 0,
2523 "open_issues": 0,
2524 "watchers": 0,
2525 "default_branch": "master"
2526 },
2527 {
2528 "id": 2862096,
2529 "name": "hajiworld",
2530 "full_name": "mreiferson/hajiworld",
2531 "owner": {
2532 "login": "mreiferson",
2533 "id": 187441,
2534 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
2535 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
2536 "url": "https://api.github.com/users/mreiferson",
2537 "html_url": "https://github.com/mreiferson",
2538 "followers_url": "https://api.github.com/users/mreiferson/followers",
2539 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
2540 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
2541 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
2542 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
2543 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
2544 "repos_url": "https://api.github.com/users/mreiferson/repos",
2545 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
2546 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
2547 "type": "User",
2548 "site_admin": false
2549 },
2550 "private": false,
2551 "html_url": "https://github.com/mreiferson/hajiworld",
2552 "description": "super mario clone (1999)",
2553 "fork": false,
2554 "url": "https://api.github.com/repos/mreiferson/hajiworld",
2555 "forks_url": "https://api.github.com/repos/mreiferson/hajiworld/forks",
2556 "keys_url": "https://api.github.com/repos/mreiferson/hajiworld/keys{/key_id}",
2557 "collaborators_url": "https://api.github.com/repos/mreiferson/hajiworld/collaborators{/collaborator}",
2558 "teams_url": "https://api.github.com/repos/mreiferson/hajiworld/teams",
2559 "hooks_url": "https://api.github.com/repos/mreiferson/hajiworld/hooks",
2560 "issue_events_url": "https://api.github.com/repos/mreiferson/hajiworld/issues/events{/number}",
2561 "events_url": "https://api.github.com/repos/mreiferson/hajiworld/events",
2562 "assignees_url": "https://api.github.com/repos/mreiferson/hajiworld/assignees{/user}",
2563 "branches_url": "https://api.github.com/repos/mreiferson/hajiworld/branches{/branch}",
2564 "tags_url": "https://api.github.com/repos/mreiferson/hajiworld/tags",
2565 "blobs_url": "https://api.github.com/repos/mreiferson/hajiworld/git/blobs{/sha}",
2566 "git_tags_url": "https://api.github.com/repos/mreiferson/hajiworld/git/tags{/sha}",
2567 "git_refs_url": "https://api.github.com/repos/mreiferson/hajiworld/git/refs{/sha}",
2568 "trees_url": "https://api.github.com/repos/mreiferson/hajiworld/git/trees{/sha}",
2569 "statuses_url": "https://api.github.com/repos/mreiferson/hajiworld/statuses/{sha}",
2570 "languages_url": "https://api.github.com/repos/mreiferson/hajiworld/languages",
2571 "stargazers_url": "https://api.github.com/repos/mreiferson/hajiworld/stargazers",
2572 "contributors_url": "https://api.github.com/repos/mreiferson/hajiworld/contributors",
2573 "subscribers_url": "https://api.github.com/repos/mreiferson/hajiworld/subscribers",
2574 "subscription_url": "https://api.github.com/repos/mreiferson/hajiworld/subscription",
2575 "commits_url": "https://api.github.com/repos/mreiferson/hajiworld/commits{/sha}",
2576 "git_commits_url": "https://api.github.com/repos/mreiferson/hajiworld/git/commits{/sha}",
2577 "comments_url": "https://api.github.com/repos/mreiferson/hajiworld/comments{/number}",
2578 "issue_comment_url": "https://api.github.com/repos/mreiferson/hajiworld/issues/comments/{number}",
2579 "contents_url": "https://api.github.com/repos/mreiferson/hajiworld/contents/{+path}",
2580 "compare_url": "https://api.github.com/repos/mreiferson/hajiworld/compare/{base}...{head}",
2581 "merges_url": "https://api.github.com/repos/mreiferson/hajiworld/merges",
2582 "archive_url": "https://api.github.com/repos/mreiferson/hajiworld/{archive_format}{/ref}",
2583 "downloads_url": "https://api.github.com/repos/mreiferson/hajiworld/downloads",
2584 "issues_url": "https://api.github.com/repos/mreiferson/hajiworld/issues{/number}",
2585 "pulls_url": "https://api.github.com/repos/mreiferson/hajiworld/pulls{/number}",
2586 "milestones_url": "https://api.github.com/repos/mreiferson/hajiworld/milestones{/number}",
2587 "notifications_url": "https://api.github.com/repos/mreiferson/hajiworld/notifications{?since,all,participating}",
2588 "labels_url": "https://api.github.com/repos/mreiferson/hajiworld/labels{/name}",
2589 "releases_url": "https://api.github.com/repos/mreiferson/hajiworld/releases{/id}",
2590 "created_at": "2011-11-27T18:05:02Z",
2591 "updated_at": "2014-01-08T14:10:43Z",
2592 "pushed_at": "2011-11-29T02:49:49Z",
2593 "git_url": "git://github.com/mreiferson/hajiworld.git",
2594 "ssh_url": "git@github.com:mreiferson/hajiworld.git",
2595 "clone_url": "https://github.com/mreiferson/hajiworld.git",
2596 "svn_url": "https://github.com/mreiferson/hajiworld",
2597 "homepage": "",
2598 "size": 27872,
2599 "stargazers_count": 1,
2600 "watchers_count": 1,
2601 "language": "C++",
2602 "has_issues": true,
2603 "has_downloads": true,
2604 "has_wiki": true,
2605 "forks_count": 0,
2606 "mirror_url": null,
2607 "open_issues_count": 0,
2608 "forks": 0,
2609 "open_issues": 0,
2610 "watchers": 1,
2611 "default_branch": "master"
2612 },
2613 {
2614 "id": 14853562,
2615 "name": "homebrew",
2616 "full_name": "mreiferson/homebrew",
2617 "owner": {
2618 "login": "mreiferson",
2619 "id": 187441,
2620 "avatar_url": "https://avatars.githubusercontent.com/u/187441?",
2621 "gravatar_id": "dd56a8e1de66aeedb987397511f830e7",
2622 "url": "https://api.github.com/users/mreiferson",
2623 "html_url": "https://github.com/mreiferson",
2624 "followers_url": "https://api.github.com/users/mreiferson/followers",
2625 "following_url": "https://api.github.com/users/mreiferson/following{/other_user}",
2626 "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}",
2627 "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}",
2628 "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions",
2629 "organizations_url": "https://api.github.com/users/mreiferson/orgs",
2630 "repos_url": "https://api.github.com/users/mreiferson/repos",
2631 "events_url": "https://api.github.com/users/mreiferson/events{/privacy}",
2632 "received_events_url": "https://api.github.com/users/mreiferson/received_events",
2633 "type": "User",
2634 "site_admin": false
2635 },
2636 "private": false,
2637 "html_url": "https://github.com/mreiferson/homebrew",
2638 "description": "The missing package manager for OS X.",
2639 "fork": true,
2640 "url": "https://api.github.com/repos/mreiferson/homebrew",
2641 "forks_url": "https://api.github.com/repos/mreiferson/homebrew/forks",
2642 "keys_url": "https://api.github.com/repos/mreiferson/homebrew/keys{/key_id}",
2643 "collaborators_url": "https://api.github.com/repos/mreiferson/homebrew/collaborators{/collaborator}",
2644 "teams_url": "https://api.github.com/repos/mreiferson/homebrew/teams",
2645 "hooks_url": "https://api.github.com/repos/mreiferson/homebrew/hooks",
2646 "issue_events_url": "https://api.github.com/repos/mreiferson/homebrew/issues/events{/number}",
2647 "events_url": "https://api.github.com/repos/mreiferson/homebrew/events",
2648 "assignees_url": "https://api.github.com/repos/mreiferson/homebrew/assignees{/user}",
2649 "branches_url": "https://api.github.com/repos/mreiferson/homebrew/branches{/branch}",
2650 "tags_url": "https://api.github.com/repos/mreiferson/homebrew/tags",
2651 "blobs_url": "https://api.github.com/repos/mreiferson/homebrew/git/blobs{/sha}",
2652 "git_tags_url": "https://api.github.com/repos/mreiferson/homebrew/git/tags{/sha}",
2653 "git_refs_url": "https://api.github.com/repos/mreiferson/homebrew/git/refs{/sha}",
2654 "trees_url": "https://api.github.com/repos/mreiferson/homebrew/git/trees{/sha}",
2655 "statuses_url": "https://api.github.com/repos/mreiferson/homebrew/statuses/{sha}",
2656 "languages_url": "https://api.github.com/repos/mreiferson/homebrew/languages",
2657 "stargazers_url": "https://api.github.com/repos/mreiferson/homebrew/stargazers",
2658 "contributors_url": "https://api.github.com/repos/mreiferson/homebrew/contributors",
2659 "subscribers_url": "https://api.github.com/repos/mreiferson/homebrew/subscribers",
2660 "subscription_url": "https://api.github.com/repos/mreiferson/homebrew/subscription",
2661 "commits_url": "https://api.github.com/repos/mreiferson/homebrew/commits{/sha}",
2662 "git_commits_url": "https://api.github.com/repos/mreiferson/homebrew/git/commits{/sha}",
2663 "comments_url": "https://api.github.com/repos/mreiferson/homebrew/comments{/number}",
2664 "issue_comment_url": "https://api.github.com/repos/mreiferson/homebrew/issues/comments/{number}",
2665 "contents_url": "https://api.github.com/repos/mreiferson/homebrew/contents/{+path}",
2666 "compare_url": "https://api.github.com/repos/mreiferson/homebrew/compare/{base}...{head}",
2667 "merges_url": "https://api.github.com/repos/mreiferson/homebrew/merges",
2668 "archive_url": "https://api.github.com/repos/mreiferson/homebrew/{archive_format}{/ref}",
2669 "downloads_url": "https://api.github.com/repos/mreiferson/homebrew/downloads",
2670 "issues_url": "https://api.github.com/repos/mreiferson/homebrew/issues{/number}",
2671 "pulls_url": "https://api.github.com/repos/mreiferson/homebrew/pulls{/number}",
2672 "milestones_url": "https://api.github.com/repos/mreiferson/homebrew/milestones{/number}",
2673 "notifications_url": "https://api.github.com/repos/mreiferson/homebrew/notifications{?since,all,participating}",
2674 "labels_url": "https://api.github.com/repos/mreiferson/homebrew/labels{/name}",
2675 "releases_url": "https://api.github.com/repos/mreiferson/homebrew/releases{/id}",
2676 "created_at": "2013-12-02T05:20:40Z",
2677 "updated_at": "2014-02-17T17:19:19Z",
2678 "pushed_at": "2014-02-17T17:06:03Z",
2679 "git_url": "git://github.com/mreiferson/homebrew.git",
2680 "ssh_url": "git@github.com:mreiferson/homebrew.git",
2681 "clone_url": "https://github.com/mreiferson/homebrew.git",
2682 "svn_url": "https://github.com/mreiferson/homebrew",
2683 "homepage": "http://brew.sh",
2684 "size": 29725,
2685 "stargazers_count": 0,
2686 "watchers_count": 0,
2687 "language": "Ruby",
2688 "has_issues": false,
2689 "has_downloads": false,
2690 "has_wiki": true,
2691 "forks_count": 0,
2692 "mirror_url": null,
2693 "open_issues_count": 0,
2694 "forks": 0,
2695 "open_issues": 0,
2696 "watchers": 0,
2697 "default_branch": "master"
2698 }
2699 ]
2700 `)
0 package snappystream
1
2 import (
3 "bytes"
4 "fmt"
5 "hash/crc32"
6 "io"
7 "io/ioutil"
8
9 "github.com/mreiferson/go-snappystream/snappy-go"
10 )
11
12 // errMssingStreamID is returned from a reader when the source stream does not
13 // begin with a stream identifier block (4.1 Stream identifier). Its occurance
14 // signifies that the source byte stream is not snappy framed.
15 var errMissingStreamID = fmt.Errorf("missing stream identifier")
16
17 type reader struct {
18 reader io.Reader
19
20 err error
21
22 seenStreamID bool
23 verifyChecksum bool
24
25 buf bytes.Buffer
26 hdr []byte
27 src []byte
28 dst []byte
29 }
30
31 // NewReader returns an io.Reader interface to the snappy framed stream format.
32 //
33 // It transparently handles reading the stream identifier (but does not proxy this
34 // to the caller), decompresses blocks, and (optionally) validates checksums.
35 //
36 // Internally, three buffers are maintained. The first two are for reading
37 // off the wrapped io.Reader and for holding the decompressed block (both are grown
38 // automatically and re-used and will never exceed the largest block size, 65536). The
39 // last buffer contains the *unread* decompressed bytes (and can grow indefinitely).
40 //
41 // The second param determines whether or not the reader will verify block
42 // checksums and can be enabled/disabled with the constants VerifyChecksum and SkipVerifyChecksum
43 //
44 // For each Read, the returned length will be up to the lesser of len(b) or 65536
45 // decompressed bytes, regardless of the length of *compressed* bytes read
46 // from the wrapped io.Reader.
47 func NewReader(r io.Reader, verifyChecksum bool) io.Reader {
48 return &reader{
49 reader: r,
50
51 verifyChecksum: verifyChecksum,
52
53 hdr: make([]byte, 4),
54 src: make([]byte, 4096),
55 dst: make([]byte, 4096),
56 }
57 }
58
59 // WriteTo implements the io.WriterTo interface used by io.Copy. It writes
60 // decoded data from the underlying reader to w. WriteTo returns the number of
61 // bytes written along with any error encountered.
62 func (r *reader) WriteTo(w io.Writer) (int64, error) {
63 if r.err != nil {
64 return 0, r.err
65 }
66
67 n, err := r.buf.WriteTo(w)
68 if err != nil {
69 // r.err doesn't need to be set because a write error occurred and the
70 // stream hasn't been corrupted.
71 return n, err
72 }
73
74 // pass a bufferFallbackWriter to nextFrame so that write errors may be
75 // recovered from, allowing the unwritten stream to be read successfully.
76 wfallback := &bufferFallbackWriter{
77 w: w,
78 buf: &r.buf,
79 }
80 for {
81 var m int
82 m, err = r.nextFrame(wfallback)
83 if wfallback.writerErr != nil && err == nil {
84 // a partial write was made before an error occurred and not all m
85 // bytes were writen to w. but decoded bytes were successfully
86 // buffered and reading can resume later.
87 n += wfallback.n
88 return n, wfallback.writerErr
89 }
90 n += int64(m)
91 if err == io.EOF {
92 return n, nil
93 }
94 if err != nil {
95 r.err = err
96 return n, err
97 }
98 }
99 panic("unreachable")
100 }
101
102 // bufferFallbackWriter writes to an underlying io.Writer until an error
103 // occurs. If a error occurs in the underlying io.Writer the value is saved
104 // for later inspection while the bufferFallbackWriter silently starts
105 // buffering all data written to it. From the caller's perspective
106 // bufferFallbackWriter has the same Write behavior has a bytes.Buffer.
107 //
108 // bufferFallbackWriter is useful for the reader.WriteTo method because it
109 // allows internal decoding routines to avoid interruption (and subsequent
110 // stream corruption) due to writing errors.
111 type bufferFallbackWriter struct {
112 w io.Writer
113 buf *bytes.Buffer
114 n int64 // number of bytes successfully written to w
115 writerErr error // any error that ocurred writing to w
116 }
117
118 // Write attempts to write b to the underlying io.Writer. If the underlying
119 // writer fails or has failed previously unwritten bytes are buffered
120 // internally. Write never returns an error but may panic with
121 // bytes.ErrTooLarge if the buffer grows too large.
122 func (w *bufferFallbackWriter) Write(b []byte) (int, error) {
123 if w.writerErr != nil {
124 return w.buf.Write(b)
125 }
126 n, err := w.w.Write(b)
127 w.n += int64(n)
128 if err != nil {
129 // begin buffering input. bytes.Buffer does not return errors and so we
130 // do not need complex error handling here.
131 w.writerErr = err
132 w.Write(b[n:])
133 return len(b), nil
134 }
135 return n, nil
136 }
137
138 func (r *reader) read(b []byte) (int, error) {
139 n, err := r.buf.Read(b)
140 r.err = err
141 return n, err
142 }
143
144 func (r *reader) Read(b []byte) (int, error) {
145 if r.err != nil {
146 return 0, r.err
147 }
148
149 if r.buf.Len() < len(b) {
150 _, r.err = r.nextFrame(&r.buf)
151 if r.err == io.EOF {
152 // fill b with any remaining bytes in the buffer.
153 return r.read(b)
154 }
155 if r.err != nil {
156 return 0, r.err
157 }
158 }
159
160 return r.read(b)
161 }
162
163 func (r *reader) nextFrame(w io.Writer) (int, error) {
164 for {
165 // read the 4-byte snappy frame header
166 _, err := io.ReadFull(r.reader, r.hdr)
167 if err != nil {
168 return 0, err
169 }
170
171 // a stream identifier may appear anywhere and contains no information.
172 // it must appear at the beginning of the stream. when found, validate
173 // it and continue to the next block.
174 if r.hdr[0] == blockStreamIdentifier {
175 err := r.readStreamID()
176 if err != nil {
177 return 0, err
178 }
179 r.seenStreamID = true
180 continue
181 }
182 if !r.seenStreamID {
183 return 0, errMissingStreamID
184 }
185
186 switch typ := r.hdr[0]; {
187 case typ == blockCompressed || typ == blockUncompressed:
188 return r.decodeBlock(w)
189 case typ == blockPadding || (0x80 <= typ && typ <= 0xfd):
190 // skip blocks whose data must not be inspected (4.4 Padding, and 4.6
191 // Reserved skippable chunks).
192 err := r.discardBlock()
193 if err != nil {
194 return 0, err
195 }
196 continue
197 default:
198 // typ must be unskippable range 0x02-0x7f. Read the block in full
199 // and return an error (4.5 Reserved unskippable chunks).
200 err = r.discardBlock()
201 if err != nil {
202 return 0, err
203 }
204 return 0, fmt.Errorf("unrecognized unskippable frame %#x", r.hdr[0])
205 }
206 }
207 panic("unreachable")
208 }
209
210 // decodeDataBlock assumes r.hdr[0] to be either blockCompressed or
211 // blockUncompressed.
212 func (r *reader) decodeBlock(w io.Writer) (int, error) {
213 // read compressed block data and determine if uncompressed data is too
214 // large.
215 buf, err := r.readBlock()
216 if err != nil {
217 return 0, err
218 }
219 declen := len(buf[4:])
220 if r.hdr[0] == blockCompressed {
221 declen, err = snappy.DecodedLen(buf[4:])
222 if err != nil {
223 return 0, err
224 }
225 }
226 if declen > MaxBlockSize {
227 return 0, fmt.Errorf("decoded block data too large %d > %d", declen, MaxBlockSize)
228 }
229
230 // decode data and verify its integrity using the little-endian crc32
231 // preceding encoded data
232 crc32le, blockdata := buf[:4], buf[4:]
233 if r.hdr[0] == blockCompressed {
234 r.dst, err = snappy.Decode(r.dst, blockdata)
235 if err != nil {
236 return 0, err
237 }
238 blockdata = r.dst
239 }
240 if r.verifyChecksum {
241 checksum := unmaskChecksum(uint32(crc32le[0]) | uint32(crc32le[1])<<8 | uint32(crc32le[2])<<16 | uint32(crc32le[3])<<24)
242 actualChecksum := crc32.Checksum(blockdata, crcTable)
243 if checksum != actualChecksum {
244 return 0, fmt.Errorf("checksum does not match %x != %x", checksum, actualChecksum)
245 }
246 }
247 return w.Write(blockdata)
248 }
249
250 func (r *reader) readStreamID() error {
251 // the length of the block is fixed so don't decode it from the header.
252 if !bytes.Equal(r.hdr, streamID[:4]) {
253 return fmt.Errorf("invalid stream identifier length")
254 }
255
256 // read the identifier block data "sNaPpY"
257 block := r.src[:6]
258 _, err := noeof(io.ReadFull(r.reader, block))
259 if err != nil {
260 return err
261 }
262 if !bytes.Equal(block, streamID[4:]) {
263 return fmt.Errorf("invalid stream identifier block")
264 }
265 return nil
266 }
267
268 func (r *reader) discardBlock() error {
269 length := uint64(decodeLength(r.hdr[1:]))
270 _, err := noeof64(io.CopyN(ioutil.Discard, r.reader, int64(length)))
271 return err
272 }
273
274 func (r *reader) readBlock() ([]byte, error) {
275 // check bounds on encoded length (+4 for checksum)
276 length := decodeLength(r.hdr[1:])
277 if length > (maxEncodedBlockSize + 4) {
278 return nil, fmt.Errorf("encoded block data too large %d > %d", length, (maxEncodedBlockSize + 4))
279 }
280
281 if int(length) > len(r.src) {
282 r.src = make([]byte, length)
283 }
284
285 buf := r.src[:length]
286 _, err := noeof(io.ReadFull(r.reader, buf))
287 if err != nil {
288 return nil, err
289 }
290
291 return buf, nil
292 }
293
294 // decodeLength decodes a 24-bit (3-byte) little-endian length from b.
295 func decodeLength(b []byte) uint32 {
296 return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16
297 }
298
299 func unmaskChecksum(c uint32) uint32 {
300 x := c - 0xa282ead8
301 return ((x >> 17) | (x << 15))
302 }
303
304 // noeof is used after reads in situations where EOF signifies invalid
305 // formatting or corruption.
306 func noeof(n int, err error) (int, error) {
307 if err == io.EOF {
308 return n, io.ErrUnexpectedEOF
309 }
310 return n, err
311 }
312
313 // noeof64 is used after long reads (e.g. io.Copy) in situations where io.EOF
314 // signifies invalid formatting or corruption.
315 func noeof64(n int64, err error) (int64, error) {
316 if err == io.EOF {
317 return n, io.ErrUnexpectedEOF
318 }
319 return n, err
320 }
0 package snappystream
1
2 import (
3 "bytes"
4 "crypto/rand"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "strings"
9 "testing"
10
11 "github.com/mreiferson/go-snappystream/snappy-go"
12 )
13
14 // This test checks that padding and reserved skippable blocks are ignored by
15 // the reader.
16 func TestReader_skippable(t *testing.T) {
17 var buf bytes.Buffer
18 // write some blocks with injected padding/skippable blocks
19 w := NewWriter(&buf)
20 write := func(p []byte) (int, error) {
21 return w.Write(p)
22 }
23 writepad := func(b byte, n int) (int, error) {
24 return buf.Write(opaqueChunk(b, n))
25 }
26 _, err := write([]byte("hello"))
27 if err != nil {
28 t.Fatalf("write error: %v", err)
29 }
30 _, err = writepad(0xfe, 100) // normal padding
31 if err != nil {
32 t.Fatalf("write error: %v", err)
33 }
34 _, err = write([]byte(" "))
35 if err != nil {
36 t.Fatalf("write error: %v", err)
37 }
38 _, err = writepad(0xa0, 100) // reserved skippable block
39 if err != nil {
40 t.Fatalf("write error: %v", err)
41 }
42 _, err = writepad(0xfe, MaxBlockSize) // normal padding
43 if err != nil {
44 t.Fatalf("write error: %v", err)
45 }
46 _, err = write([]byte("padding"))
47 if err != nil {
48 t.Fatalf("write error: %v", err)
49 }
50
51 p, err := ioutil.ReadAll(NewReader(&buf, true))
52 if err != nil {
53 t.Fatalf("read error: %v", err)
54 }
55 if string(p) != "hello padding" {
56 t.Fatalf("read: unexpected content %q", string(p))
57 }
58 }
59
60 // This test checks that reserved unskippable blocks are cause decoder errors.
61 func TestReader_unskippable(t *testing.T) {
62 var buf bytes.Buffer
63 // write some blocks with injected padding/skippable blocks
64 w := NewWriter(&buf)
65 write := func(p []byte) (int, error) {
66 return w.Write(p)
67 }
68 writepad := func(b byte, n int) (int, error) {
69 return buf.Write(opaqueChunk(b, n))
70 }
71 _, err := write([]byte("unskippable"))
72 if err != nil {
73 t.Fatalf("write error: %v", err)
74 }
75 _, err = writepad(0x50, 100) // unskippable reserved block
76 if err != nil {
77 t.Fatalf("write error: %v", err)
78 }
79 _, err = write([]byte(" blocks"))
80 if err != nil {
81 t.Fatalf("write error: %v", err)
82 }
83
84 _, err = ioutil.ReadAll(NewReader(&buf, true))
85 if err == nil {
86 t.Fatalf("read success")
87 }
88 }
89
90 func TestReaderStreamID(t *testing.T) {
91 data := []byte("a snappy-framed data stream")
92 var buf bytes.Buffer
93 w := NewWriter(&buf)
94 _, err := w.Write(data)
95 if err != nil {
96 t.Fatal(err)
97 }
98
99 stream := buf.Bytes()
100
101 // sanity check: the stream can be decoded and starts with streamID
102 r := NewReader(bytes.NewReader(stream), true)
103 _, err = ioutil.ReadAll(r)
104 if err != nil {
105 t.Fatalf("read: %v", err)
106 }
107 if !bytes.HasPrefix(stream, streamID) {
108 t.Fatal("missing stream id")
109 }
110
111 // streamNoID is valid except for a missing the streamID block
112 streamNoID := bytes.TrimPrefix(stream, streamID)
113 r = NewReader(bytes.NewReader(streamNoID), true)
114 n, err := r.Read(make([]byte, 1))
115 if err == nil {
116 t.Fatalf("read: expected an error reading input missing a stream identifier block")
117 }
118 if n != 0 {
119 t.Fatalf("read: read non-zero number of bytes %d", n)
120 }
121 n, err = r.Read(make([]byte, 1))
122 if err == nil {
123 t.Fatalf("read: successful read after missing stream id error")
124 }
125 if n != 0 {
126 t.Fatalf("read: read non-zero number of bytes %d after missing stream id error", n)
127 }
128 }
129
130 // This test validates the reader successfully decods a padding of maximal
131 // size, 2^24 - 1.
132 func TestReader_maxPad(t *testing.T) {
133 buf := bytes.NewReader(bytes.Join([][]byte{
134 streamID,
135 compressedChunk(t, []byte("a maximal padding chunk")),
136 opaqueChunk(0xfe, (1<<24)-1), // normal padding
137 compressedChunk(t, []byte(" is decoded successfully")),
138 }, nil))
139 r := NewReader(buf, true)
140 p, err := ioutil.ReadAll(r)
141 if err != nil {
142 t.Fatalf("read error: %v", err)
143 }
144 if string(p) != "a maximal padding chunk is decoded successfully" {
145 t.Fatalf("read: unexpected content %q", string(p))
146 }
147 }
148
149 // This test validates the reader successfully decodes a skippable chunk of
150 // maximal size, 2^24 - 1.
151 func TestReader_maxSkippable(t *testing.T) {
152 buf := bytes.NewReader(bytes.Join([][]byte{
153 streamID,
154 compressedChunk(t, []byte("a maximal skippable chunk")),
155 opaqueChunk(0xce, (1<<24)-1), // reserved skippable chunk
156 compressedChunk(t, []byte(" is decoded successfully")),
157 }, nil))
158 r := NewReader(buf, true)
159 p, err := ioutil.ReadAll(r)
160 if err != nil {
161 t.Fatalf("read error: %v", err)
162 }
163 if string(p) != "a maximal skippable chunk is decoded successfully" {
164 t.Fatalf("read: unexpected content %q", string(p))
165 }
166 }
167
168 // TestReader_maxBlock validates bounds checking on encoded and decoded data
169 // (4.2 Compressed Data).
170 func TestReader_maxBlock(t *testing.T) {
171 // decompressing a block with compressed length greater than MaxBlockSize
172 // should succeed.
173 buf := bytes.NewReader(bytes.Join([][]byte{
174 streamID,
175 compressedChunkGreaterN(t, MaxBlockSize),
176 }, nil))
177 r := NewReader(buf, true)
178 b, err := ioutil.ReadAll(r)
179 if err != nil {
180 t.Fatal(err)
181 }
182 if len(b) != MaxBlockSize {
183 t.Fatalf("bad read (%d bytes)", len(b))
184 }
185
186 // decompressing should fail if the block with decompressed length greater
187 // than MaxBlockSize.
188 buf = bytes.NewReader(bytes.Join([][]byte{
189 streamID,
190 compressedChunk(t, make([]byte, MaxBlockSize+1)),
191 }, nil))
192 r = NewReader(buf, true)
193 b, err = ioutil.ReadAll(r)
194 if err == nil {
195 t.Fatal("unexpected success")
196 }
197 if len(b) > 0 {
198 t.Fatalf("unexpected read %q", b)
199 }
200 }
201
202 // This test validates the reader's behavior encountering unskippable chunks of
203 // maximal size, 2^24 - 1. The desired error to in this case is one reporting
204 // an unskippable chunk, not a length error.
205 func TestReader_maxUnskippable(t *testing.T) {
206 // the first block should be decoded successfully.
207 prefix := "a maximal unskippable chunk"
208 buf := bytes.NewReader(bytes.Join([][]byte{
209 streamID,
210 compressedChunk(t, []byte(prefix)),
211 opaqueChunk(0x03, (1<<24)-1), // low end of the unskippable range
212 compressedChunk(t, []byte(" failure must be reported as such")),
213 }, nil))
214 p := make([]byte, len(prefix))
215 r := NewReader(buf, true)
216 n, err := r.Read(p)
217 if err != nil {
218 t.Fatalf("read error: %v", err)
219 }
220 if n != len(p) {
221 t.Fatalf("read: short read %d", n)
222 }
223 if string(p) != prefix {
224 t.Fatalf("read: bad value %q", p)
225 }
226
227 n, err = r.Read(p)
228 if err == nil {
229 t.Fatalf("read: expected error")
230 }
231 if n > 0 {
232 t.Fatalf("read: read %d more bytes than expected", n)
233 }
234 if !strings.Contains(err.Error(), "unskippable") {
235 t.Fatalf("read error: %v", err)
236 }
237 }
238
239 // This test validates errors returned when data blocks exceed size limits.
240 func TestReader_blockTooLarge(t *testing.T) {
241 // the compressed chunk size is within the allowed encoding size
242 // (maxEncodedBlockSize). but the uncompressed data is larger than allowed.
243 badstream := bytes.Join([][]byte{
244 streamID,
245 compressedChunk(t, make([]byte, (1<<24)-5)),
246 }, nil)
247 r := NewReader(bytes.NewBuffer(badstream), true)
248 p := make([]byte, 1)
249 n, err := r.Read(p)
250 if err == nil {
251 t.Fatalf("read: expected error")
252 }
253 if n != 0 {
254 t.Fatalf("read: read data from the stream")
255 }
256
257 // the compressed chunk size is within the allowed encoding size
258 // (maxEncodedBlockSize). but the uncompressed data is larger than allowed.
259 badstream = bytes.Join([][]byte{
260 streamID,
261 uncompressedChunk(t, make([]byte, (1<<24)-5)),
262 }, nil)
263 r = NewReader(bytes.NewBuffer(badstream), true)
264 p = make([]byte, 1)
265 n, err = r.Read(p)
266 if err == nil {
267 t.Fatalf("read: expected error")
268 }
269 if n != 0 {
270 t.Fatalf("read: read data from the stream")
271 }
272 }
273
274 // This test validates the reader's handling of corrupt chunks.
275 func TestReader_corruption(t *testing.T) {
276 // corruptID is a corrupt stream identifier
277 corruptID := append([]byte(nil), streamID...)
278 corruptID = bytes.Replace(streamID, []byte("p"), []byte("P"), -1) // corrupt "sNaPpY" data
279 badstream := corruptID
280
281 r := NewReader(bytes.NewBuffer(badstream), true)
282 p := make([]byte, 1)
283 n, err := r.Read(p)
284 if err == nil {
285 t.Fatalf("read: expected error")
286 }
287 if err == io.EOF {
288 t.Fatalf("read: unexpected eof")
289 }
290 if n != 0 {
291 t.Fatalf("read: read data from the stream")
292 }
293
294 corruptID = append([]byte(nil), streamID...) // corrupt the length
295 corruptID[1] = 0x00
296 badstream = corruptID
297
298 r = NewReader(bytes.NewBuffer(badstream), true)
299 p = make([]byte, 1)
300 n, err = r.Read(p)
301 if err == nil {
302 t.Fatalf("read: expected error")
303 }
304 if err == io.EOF {
305 t.Fatalf("read: unexpected eof")
306 }
307 if n != 0 {
308 t.Fatalf("read: read data from the stream")
309 }
310
311 // chunk is a valid compressed block
312 chunk := compressedChunk(t, []byte("a data block"))
313
314 // corrupt is a corrupt chunk
315 corrupt := append([]byte(nil), chunk...)
316 copy(corrupt[8:], make([]byte, 10)) // corrupt snappy-encoded data
317 badstream = bytes.Join([][]byte{
318 streamID,
319 corrupt,
320 }, nil)
321
322 r = NewReader(bytes.NewBuffer(badstream), true)
323 p = make([]byte, 1)
324 n, err = r.Read(p)
325 if err == nil {
326 t.Fatalf("read: expected error")
327 }
328 if err == io.EOF {
329 t.Fatalf("read: unexpected eof")
330 }
331 if n != 0 {
332 t.Fatalf("read: read data from the stream")
333 }
334
335 corrupt = append([]byte(nil), chunk...)
336 copy(corrupt[4:8], make([]byte, 4)) // crc checksum failure
337 badstream = bytes.Join([][]byte{
338 streamID,
339 corrupt,
340 }, nil)
341
342 r = NewReader(bytes.NewBuffer(badstream), true)
343 p = make([]byte, 1)
344 n, err = r.Read(p)
345 if err == nil {
346 t.Fatalf("read: expected error")
347 }
348 if err == io.EOF {
349 t.Fatalf("read: unexpected eof")
350 }
351 if n != 0 {
352 t.Fatalf("read: read data from the stream")
353 }
354 }
355
356 // This test ensures that reader returns io.ErrUnexpectedEOF at the appropriate
357 // times. io.EOF must be reserved for the case when all data has been
358 // successfully decoded.
359 func TestReader_unexpectedEOF(t *testing.T) {
360 var decodeBuffer [64 << 10]byte
361
362 for _, test := range [][]byte{
363 // truncated streamIDs
364 streamID[:4],
365 streamID[:len(streamID)-1],
366
367 // truncated data blocks
368 bytes.Join([][]byte{
369 streamID,
370 compressedChunk(t, bytes.Repeat([]byte("abc"), 100))[:2],
371 }, nil),
372 bytes.Join([][]byte{
373 streamID,
374 compressedChunk(t, bytes.Repeat([]byte("abc"), 100))[:7],
375 }, nil),
376
377 // truncated padding
378 bytes.Join([][]byte{
379 streamID,
380 opaqueChunk(0xfe, 100)[:1],
381 }, nil),
382 bytes.Join([][]byte{
383 streamID,
384 opaqueChunk(0xfe, 100)[:8],
385 }, nil),
386
387 // truncated skippable chunk
388 bytes.Join([][]byte{
389 streamID,
390 opaqueChunk(0xcf, 100)[:3],
391 }, nil),
392 bytes.Join([][]byte{
393 streamID,
394 opaqueChunk(0xcf, 100)[:7],
395 }, nil),
396
397 // truncated unskippable chunk
398 bytes.Join([][]byte{
399 streamID,
400 opaqueChunk(0x03, 100)[:3],
401 }, nil),
402 bytes.Join([][]byte{
403 streamID,
404 opaqueChunk(0x03, 100)[:5],
405 }, nil),
406 } {
407 r := NewReader(bytes.NewReader(test), true)
408 n, err := r.Read(decodeBuffer[:])
409 if err == nil {
410 t.Errorf("read bad streamID: expected error")
411 }
412 if err != io.ErrUnexpectedEOF {
413 t.Errorf("read bad streamID: %v", err)
414 }
415 if n != 0 {
416 t.Errorf("read bad streamID: expected read length %d", n)
417 }
418 }
419 }
420
421 var errNotEnoughEntropy = fmt.Errorf("inadequate entropy in PRNG")
422
423 // compressedChunkGreaterN like compressedChunk produces a single, compressed,
424 // snappy-framed block. The returned block will have decoded length at most n
425 // and encoded length greater than n.
426 func compressedChunkGreaterN(t *testing.T, n int) []byte {
427 decoded := make([]byte, n)
428 var numTries int
429 var encoded []byte
430 for len(encoded) <= n && numTries < 3 {
431 numTries++
432 nrd, err := io.ReadFull(rand.Reader, decoded)
433 if err != nil {
434 t.Errorf("crypto/rand: %v", err)
435 return nil
436 }
437 if nrd != n {
438 t.Errorf("crypto/rand: bad read (%d bytes)", nrd)
439 return nil
440 }
441 encoded, err = snappy.Encode(encoded[:cap(encoded)], decoded)
442 if err != nil {
443 t.Errorf("snappy: %v", err)
444 return nil
445 }
446 }
447 if len(encoded) <= n {
448 t.Error(errNotEnoughEntropy)
449 return nil
450 }
451
452 return compressedChunk(t, decoded)
453 }
454
455 // compressedChunk encodes b returning a single, compressed, snappy-framed
456 // block. compressedChunk can encode source data larger than allowed in the
457 // specification.
458 func compressedChunk(t *testing.T, src []byte) []byte {
459 encoded, err := snappy.Encode(nil, src)
460 if err != nil {
461 t.Errorf("snappy: %v", err)
462 return nil
463 }
464
465 if len(encoded) > (1<<24)-5 { // account for the 4-byte checksum
466 t.Errorf("block data too large %d", len(src))
467 return nil
468 }
469
470 chunk := make([]byte, len(encoded)+8)
471 writeHeader(chunk[:8], blockCompressed, encoded, src)
472 copy(chunk[8:], encoded)
473 return chunk
474 }
475
476 // uncompressedChunk encodes b returning a single, uncompressed, snappy-framed
477 // block. uncompressedChunk can encode chunks larger than allowed by the
478 // specification.
479 func uncompressedChunk(t *testing.T, src []byte) []byte {
480 if len(src) > (1<<24)-5 { // account for the 4-byte checksum
481 t.Errorf("block data too large %d", len(src))
482 return nil
483 }
484
485 chunk := make([]byte, len(src)+8)
486 writeHeader(chunk[:8], blockUncompressed, src, src)
487 copy(chunk[8:], src)
488 return chunk
489 }
490
491 // opaqueChunk returns an opaque b chunk (e.g. padding 0xfe) with length n
492 // (total length, n+4 bytes). practically useless but good enough for testing.
493 // the first 4-bytes of data are random to ensure checksums are not being
494 // verified.
495 func opaqueChunk(b byte, n int) []byte {
496 if b == 0 {
497 b = 0xfe
498 }
499
500 length := uint32(n)
501 lengthle := []byte{byte(length), byte(length >> 8), byte(length >> 16)}
502 checksum := make([]byte, 4)
503 _, err := rand.Read(checksum)
504 if err != nil {
505 panic(err)
506 }
507 padbytes := make([]byte, n-4) // let this panic if n < 4
508 _, err = rand.Read(padbytes)
509 if err != nil {
510 panic(err)
511 }
512
513 var h []byte
514 h = append(h, b)
515 h = append(h, lengthle...)
516 h = append(h, checksum...)
517 h = append(h, padbytes...)
518 return h
519 }
520
521 func TestReaderWriteTo(t *testing.T) {
522 var encbuf bytes.Buffer
523 var decbuf bytes.Buffer
524 msg := "hello copy interface"
525
526 w := NewWriter(&encbuf)
527 n, err := io.WriteString(w, msg)
528 if err != nil {
529 t.Fatalf("encode: %v", err)
530 }
531 if n != len(msg) {
532 t.Fatalf("encode: %v", err)
533 }
534
535 r := NewReader(&encbuf, true)
536 n64, err := r.(*reader).WriteTo(&decbuf)
537 if err != nil {
538 t.Fatalf("decode: %v", err)
539 }
540 if n64 != int64(len(msg)) {
541 t.Fatalf("decode: decoded %d bytes %q", n64, decbuf.Bytes())
542 }
543
544 decmsg := decbuf.String()
545 if decmsg != msg {
546 t.Fatalf("decode: %q", decmsg)
547 }
548 }
549
550 func TestReaderWriteToPreviousError(t *testing.T) {
551 // construct an io.Reader that returns an error on the first read and a
552 // valid snappy-framed stream on subsequent reads.
553 var stream io.Reader
554 stream = encodedString("hello")
555 stream = readErrorFirst(stream, fmt.Errorf("one time error"))
556 stream = NewReader(stream, true)
557
558 var buf bytes.Buffer
559
560 // attempt the first read from the stream.
561 n, err := stream.(*reader).WriteTo(&buf)
562 if err == nil {
563 t.Fatalf("error expected")
564 }
565 if n != 0 {
566 t.Fatalf("bytes written to buffer: %q", buf.Bytes())
567 }
568
569 // attempt a second read from the stream.
570 n, err = stream.(*reader).WriteTo(&buf)
571 if err == nil {
572 t.Fatalf("error expected")
573 }
574 if n != 0 {
575 t.Fatalf("bytes written to buffer: %q", buf.Bytes())
576 }
577 }
578
579 // readerErrorFirst is an io.Reader that returns an error on the first read.
580 // readerErrorFirst is used to test that a reader does not attempt to read
581 // after a read error occurs.
582 type readerErrorFirst struct {
583 r io.Reader
584 err error
585 count int
586 }
587
588 func readErrorFirst(r io.Reader, err error) io.Reader {
589 return &readerErrorFirst{
590 r: r,
591 err: err,
592 }
593 }
594
595 func (r *readerErrorFirst) Read(b []byte) (int, error) {
596 r.count++
597 if r.count == 1 {
598 return 0, r.err
599 }
600 return r.r.Read(b)
601 }
602
603 func TestReaderWriteToWriteError(t *testing.T) {
604 origmsg := "hello"
605 stream := NewReader(encodedString(origmsg), true)
606
607 // attempt to write the stream to an io.Writer that will not accept input.
608 n, err := stream.(*reader).WriteTo(unwritable(fmt.Errorf("cannot write to this writer")))
609 if err == nil {
610 t.Fatalf("error expected")
611 }
612 if n != 0 {
613 t.Fatalf("reported %d written to an unwritable writer", n)
614 }
615
616 // the decoded message can still be read successfully because the encoded
617 // stream was not corrupt/broken.
618 var buf bytes.Buffer
619 n, err = stream.(*reader).WriteTo(&buf)
620 if err != nil {
621 t.Errorf("unexpected error: %v", err)
622 }
623 if n != int64(len(origmsg)) {
624 t.Errorf("read %d bytes", n)
625 }
626 if buf.String() != origmsg {
627 t.Errorf("read %q", buf)
628 }
629 }
630
631 // writerUnwritable is an io.Writer that always returns an error.
632 type writerUnwritable struct {
633 err error
634 }
635
636 func (w *writerUnwritable) Write([]byte) (int, error) {
637 return 0, w.err
638 }
639
640 func unwritable(err error) io.Writer {
641 return &writerUnwritable{err}
642 }
643
644 func encodedString(s string) io.Reader {
645 var buf bytes.Buffer
646 w := NewWriter(&buf)
647 io.WriteString(w, s)
648 return &buf
649 }
0 package snappystream
1
2 import (
3 "bytes"
4 "crypto/rand"
5 "io"
6 "io/ioutil"
7 "testing"
8 )
9
10 const TestFileSize = 10 << 20 // 10MB
11
12 // dummyBytesReader returns an io.Reader that avoids buffering optimizations
13 // in io.Copy. This can be considered a 'worst-case' io.Reader as far as writer
14 // frame alignment goes.
15 //
16 // Note: io.Copy uses a 32KB buffer internally as of Go 1.3, but that isn't
17 // part of its public API (undocumented).
18 func dummyBytesReader(p []byte) io.Reader {
19 return ioutil.NopCloser(bytes.NewReader(p))
20 }
21
22 func testWriteThenRead(t *testing.T, name string, bs []byte) {
23 var buf bytes.Buffer
24 w := NewWriter(&buf)
25 n, err := io.Copy(w, dummyBytesReader(bs))
26 if err != nil {
27 t.Errorf("write %v: %v", name, err)
28 return
29 }
30 if n != int64(len(bs)) {
31 t.Errorf("write %v: wrote %d bytes (!= %d)", name, n, len(bs))
32 return
33 }
34
35 enclen := buf.Len()
36
37 r := NewReader(&buf, true)
38 gotbs, err := ioutil.ReadAll(r)
39 if err != nil {
40 t.Errorf("read %v: %v", name, err)
41 return
42 }
43 n = int64(len(gotbs))
44 if n != int64(len(bs)) {
45 t.Errorf("read %v: read %d bytes (!= %d)", name, n, len(bs))
46 return
47 }
48
49 if !bytes.Equal(gotbs, bs) {
50 t.Errorf("%v: unequal decompressed content", name)
51 return
52 }
53
54 c := float64(len(bs)) / float64(enclen)
55 t.Logf("%v compression ratio %.03g (%d byte reduction)", name, c, len(bs)-enclen)
56 }
57
58 func testBufferedWriteThenRead(t *testing.T, name string, bs []byte) {
59 var buf bytes.Buffer
60 w := NewBufferedWriter(&buf)
61 n, err := io.Copy(w, dummyBytesReader(bs))
62 if err != nil {
63 t.Errorf("write %v: %v", name, err)
64 return
65 }
66 if n != int64(len(bs)) {
67 t.Errorf("write %v: wrote %d bytes (!= %d)", name, n, len(bs))
68 return
69 }
70 err = w.Close()
71 if err != nil {
72 t.Errorf("close %v: %v", name, err)
73 return
74 }
75
76 enclen := buf.Len()
77
78 r := NewReader(&buf, true)
79 gotbs, err := ioutil.ReadAll(r)
80 if err != nil {
81 t.Errorf("read %v: %v", name, err)
82 return
83 }
84 n = int64(len(gotbs))
85 if n != int64(len(bs)) {
86 t.Errorf("read %v: read %d bytes (!= %d)", name, n, len(bs))
87 return
88 }
89
90 if !bytes.Equal(gotbs, bs) {
91 t.Errorf("%v: unequal decompressed content", name)
92 return
93 }
94
95 c := float64(len(bs)) / float64(enclen)
96 t.Logf("%v compression ratio %.03g (%d byte reduction)", name, c, len(bs)-enclen)
97 }
98
99 func TestWriterReader(t *testing.T) {
100 testWriteThenRead(t, "simple", []byte("test"))
101 testWriteThenRead(t, "manpage", testDataMan)
102 testWriteThenRead(t, "json", testDataJSON)
103
104 p := make([]byte, TestFileSize)
105 testWriteThenRead(t, "constant", p)
106
107 _, err := rand.Read(p)
108 if err != nil {
109 t.Fatal(err)
110 }
111 testWriteThenRead(t, "random", p)
112
113 }
114
115 func TestBufferedWriterReader(t *testing.T) {
116 testBufferedWriteThenRead(t, "simple", []byte("test"))
117 testBufferedWriteThenRead(t, "manpage", testDataMan)
118 testBufferedWriteThenRead(t, "json", testDataJSON)
119
120 p := make([]byte, TestFileSize)
121 testBufferedWriteThenRead(t, "constant", p)
122
123 _, err := rand.Read(p)
124 if err != nil {
125 t.Fatal(err)
126 }
127 testBufferedWriteThenRead(t, "random", p)
128
129 }
130
131 func TestWriterChunk(t *testing.T) {
132 var buf bytes.Buffer
133
134 in := make([]byte, 128000)
135
136 w := NewWriter(&buf)
137 r := NewReader(&buf, VerifyChecksum)
138
139 n, err := w.Write(in)
140 if err != nil {
141 t.Fatalf(err.Error())
142 }
143 if n != len(in) {
144 t.Fatalf("wrote wrong amount %d != %d", n, len(in))
145 }
146
147 out := make([]byte, len(in))
148 n, err = io.ReadFull(r, out)
149 if err != nil {
150 t.Fatal(err)
151 }
152 if n != len(in) {
153 t.Fatalf("read wrong amount %d != %d", n, len(in))
154 }
155
156 if !bytes.Equal(out, in) {
157 t.Fatalf("bytes not equal %v != %v", out, in)
158 }
159 }
160
161 func BenchmarkWriterManpage(b *testing.B) {
162 benchmarkWriterBytes(b, testDataMan)
163 }
164 func BenchmarkBufferedWriterManpage(b *testing.B) {
165 benchmarkBufferedWriterBytes(b, testDataMan)
166 }
167 func BenchmarkBufferedWriterManpageNoCopy(b *testing.B) {
168 benchmarkBufferedWriterBytesNoCopy(b, testDataMan)
169 }
170
171 func BenchmarkWriterJSON(b *testing.B) {
172 benchmarkWriterBytes(b, testDataJSON)
173 }
174 func BenchmarkBufferedWriterJSON(b *testing.B) {
175 benchmarkBufferedWriterBytes(b, testDataJSON)
176 }
177 func BenchmarkBufferedWriterJSONNoCopy(b *testing.B) {
178 benchmarkBufferedWriterBytesNoCopy(b, testDataJSON)
179 }
180
181 // BenchmarkWriterRandom tests performance encoding effectively uncompressable
182 // data.
183 func BenchmarkWriterRandom(b *testing.B) {
184 benchmarkWriterBytes(b, randBytes(b, TestFileSize))
185 }
186 func BenchmarkBufferedWriterRandom(b *testing.B) {
187 benchmarkBufferedWriterBytes(b, randBytes(b, TestFileSize))
188 }
189 func BenchmarkBufferedWriterRandomNoCopy(b *testing.B) {
190 benchmarkBufferedWriterBytesNoCopy(b, randBytes(b, TestFileSize))
191 }
192
193 // BenchmarkWriterConstant tests performance encoding maximally compressible
194 // data.
195 func BenchmarkWriterConstant(b *testing.B) {
196 benchmarkWriterBytes(b, make([]byte, TestFileSize))
197 }
198 func BenchmarkBufferedWriterConstant(b *testing.B) {
199 benchmarkBufferedWriterBytes(b, make([]byte, TestFileSize))
200 }
201 func BenchmarkBufferedWriterConstantNoCopy(b *testing.B) {
202 benchmarkBufferedWriterBytesNoCopy(b, make([]byte, TestFileSize))
203 }
204
205 func benchmarkWriterBytes(b *testing.B, p []byte) {
206 enc := func() io.WriteCloser {
207 // wrap the normal writer so that it has a noop Close method. writer
208 // does not implement ReaderFrom so this does not impact performance.
209 return &nopWriteCloser{NewWriter(ioutil.Discard)}
210 }
211 benchmarkEncode(b, enc, p)
212 }
213 func benchmarkBufferedWriterBytes(b *testing.B, p []byte) {
214 enc := func() io.WriteCloser {
215 // the writer's ReaderFrom implemention will be used in the benchmark.
216 return NewBufferedWriter(ioutil.Discard)
217 }
218 benchmarkEncode(b, enc, p)
219 }
220 func benchmarkBufferedWriterBytesNoCopy(b *testing.B, p []byte) {
221 enc := func() io.WriteCloser {
222 // the writer is wrapped as to hide it's ReaderFrom implemention.
223 return &writeCloserNoCopy{NewBufferedWriter(ioutil.Discard)}
224 }
225 benchmarkEncode(b, enc, p)
226 }
227
228 // benchmarkEncode benchmarks the speed at which bytes can be copied from
229 // bs into writers created by enc.
230 func benchmarkEncode(b *testing.B, enc func() io.WriteCloser, bs []byte) {
231 size := int64(len(bs))
232 b.SetBytes(size)
233 b.StartTimer()
234 for i := 0; i < b.N; i++ {
235 w := enc()
236 n, err := io.Copy(w, dummyBytesReader(bs))
237 if err != nil {
238 b.Fatal(err)
239 }
240 if n != size {
241 b.Fatalf("wrote wrong amount %d != %d", n, size)
242 }
243 err = w.Close()
244 if err != nil {
245 b.Fatalf("close: %v", err)
246 }
247 }
248 b.StopTimer()
249 }
250
251 func BenchmarkReaderManpage(b *testing.B) {
252 encodeAndBenchmarkReader(b, testDataMan)
253 }
254 func BenchmarkReaderManpage_buffered(b *testing.B) {
255 encodeAndBenchmarkReader_buffered(b, testDataMan)
256 }
257 func BenchmarkReaderManpageNoCopy(b *testing.B) {
258 encodeAndBenchmarkReaderNoCopy(b, testDataMan)
259 }
260
261 func BenchmarkReaderJSON(b *testing.B) {
262 encodeAndBenchmarkReader(b, testDataJSON)
263 }
264 func BenchmarkReaderJSON_buffered(b *testing.B) {
265 encodeAndBenchmarkReader_buffered(b, testDataJSON)
266 }
267 func BenchmarkReaderJSONNoCopy(b *testing.B) {
268 encodeAndBenchmarkReaderNoCopy(b, testDataJSON)
269 }
270
271 // BenchmarkReaderRandom tests decoding of effectively uncompressable data.
272 func BenchmarkReaderRandom(b *testing.B) {
273 encodeAndBenchmarkReader(b, randBytes(b, TestFileSize))
274 }
275 func BenchmarkReaderRandom_buffered(b *testing.B) {
276 encodeAndBenchmarkReader_buffered(b, randBytes(b, TestFileSize))
277 }
278 func BenchmarkReaderRandomNoCopy(b *testing.B) {
279 encodeAndBenchmarkReaderNoCopy(b, randBytes(b, TestFileSize))
280 }
281
282 // BenchmarkReaderConstant tests decoding of maximally compressible data.
283 func BenchmarkReaderConstant(b *testing.B) {
284 encodeAndBenchmarkReader(b, make([]byte, TestFileSize))
285 }
286 func BenchmarkReaderConstant_buffered(b *testing.B) {
287 encodeAndBenchmarkReader_buffered(b, make([]byte, TestFileSize))
288 }
289 func BenchmarkReaderConstantNoCopy(b *testing.B) {
290 encodeAndBenchmarkReaderNoCopy(b, make([]byte, TestFileSize))
291 }
292
293 // encodeAndBenchmarkReader is a helper that benchmarks the package
294 // reader's performance given p encoded as a snappy framed stream.
295 //
296 // encodeAndBenchmarkReader benchmarks decoding of streams containing
297 // (multiple) short frames.
298 func encodeAndBenchmarkReader(b *testing.B, p []byte) {
299 enc, err := encodeStreamBytes(p, false)
300 if err != nil {
301 b.Fatalf("pre-benchmark compression: %v", err)
302 }
303 dec := func(r io.Reader) io.Reader {
304 return NewReader(r, VerifyChecksum)
305 }
306 benchmarkDecode(b, dec, int64(len(p)), enc)
307 }
308
309 // encodeAndBenchmarkReader_buffered is a helper that benchmarks the
310 // package reader's performance given p encoded as a snappy framed stream.
311 //
312 // encodeAndBenchmarkReader_buffered benchmarks decoding of streams that
313 // contain at most one short frame (at the end).
314 func encodeAndBenchmarkReader_buffered(b *testing.B, p []byte) {
315 enc, err := encodeStreamBytes(p, true)
316 if err != nil {
317 b.Fatalf("pre-benchmark compression: %v", err)
318 }
319 dec := func(r io.Reader) io.Reader {
320 return NewReader(r, VerifyChecksum)
321 }
322 benchmarkDecode(b, dec, int64(len(p)), enc)
323 }
324
325 // encodeAndBenchmarkReaderNoCopy is a helper that benchmarks the
326 // package reader's performance given p encoded as a snappy framed stream.
327 // encodeAndBenchmarReaderNoCopy avoids use of the reader's io.WriterTo
328 // interface.
329 //
330 // encodeAndBenchmarkReaderNoCopy benchmarks decoding of streams that
331 // contain at most one short frame (at the end).
332 func encodeAndBenchmarkReaderNoCopy(b *testing.B, p []byte) {
333 enc, err := encodeStreamBytes(p, true)
334 if err != nil {
335 b.Fatalf("pre-benchmark compression: %v", err)
336 }
337 dec := func(r io.Reader) io.Reader {
338 return ioutil.NopCloser(NewReader(r, VerifyChecksum))
339 }
340 benchmarkDecode(b, dec, int64(len(p)), enc)
341 }
342
343 // benchmarkDecode runs a benchmark that repeatedly decoded snappy
344 // framed bytes enc. The length of the decoded result in each iteration must
345 // equal size.
346 func benchmarkDecode(b *testing.B, dec func(io.Reader) io.Reader, size int64, enc []byte) {
347 b.SetBytes(int64(len(enc))) // BUG this is probably wrong
348 b.ResetTimer()
349 for i := 0; i < b.N; i++ {
350 r := dec(bytes.NewReader(enc))
351 n, err := io.Copy(ioutil.Discard, r)
352 if err != nil {
353 b.Fatalf(err.Error())
354 }
355 if n != size {
356 b.Fatalf("read wrong amount %d != %d", n, size)
357 }
358 }
359 b.StopTimer()
360 }
361
362 // encodeStreamBytes is like encodeStream but operates on a byte slice.
363 // encodeStreamBytes ensures that long streams are not maximally compressed if
364 // buffer is false.
365 func encodeStreamBytes(b []byte, buffer bool) ([]byte, error) {
366 return encodeStream(dummyBytesReader(b), buffer)
367 }
368
369 // encodeStream encodes data read from r as a snappy framed stream and returns
370 // the result as a byte slice. if buffer is true the bytes from r are buffered
371 // to improve the resulting slice's compression ratio.
372 func encodeStream(r io.Reader, buffer bool) ([]byte, error) {
373 var buf bytes.Buffer
374 if !buffer {
375 w := NewWriter(&buf)
376 _, err := io.Copy(w, r)
377 if err != nil {
378 return nil, err
379 }
380 return buf.Bytes(), nil
381 }
382
383 w := NewBufferedWriter(&buf)
384 _, err := io.Copy(w, r)
385 if err != nil {
386 return nil, err
387 }
388 err = w.Close()
389 if err != nil {
390 return nil, err
391 }
392 return buf.Bytes(), nil
393 }
394
395 // randBytes reads size bytes from the computer's cryptographic random source.
396 // the resulting bytes have approximately maximal entropy and are effectively
397 // uncompressible with any algorithm.
398 func randBytes(b *testing.B, size int) []byte {
399 randp := make([]byte, size)
400 _, err := io.ReadFull(rand.Reader, randp)
401 if err != nil {
402 b.Fatal(err)
403 }
404 return randp
405 }
406
407 // writeCloserNoCopy is an io.WriteCloser that simply wraps another
408 // io.WriteCloser. This is useful for masking implementations for interfaces
409 // like ReaderFrom which may be opted into use inside functions like io.Copy.
410 type writeCloserNoCopy struct {
411 io.WriteCloser
412 }
413
414 // nopWriteCloser is an io.WriteCloser that has a noop Close method. This type
415 // has the effect of masking the underlying writer's Close implementation if it
416 // has one, or satisfying interface implementations for writers that do not
417 // need to be closing.
418 type nopWriteCloser struct {
419 io.Writer
420 }
421
422 func (w *nopWriteCloser) Close() error {
423 return nil
424 }
0 // Copyright 2011 The Snappy-Go Authors. All rights reserved.
1 // Use of this source code is governed by a BSD-style
2 // license that can be found in the LICENSE file.
3
4 package snappy
5
6 import (
7 "encoding/binary"
8 "errors"
9 )
10
11 // ErrCorrupt reports that the input is invalid.
12 var ErrCorrupt = errors.New("snappy: corrupt input")
13
14 // DecodedLen returns the length of the decoded block.
15 func DecodedLen(src []byte) (int, error) {
16 v, _, err := decodedLen(src)
17 return v, err
18 }
19
20 // decodedLen returns the length of the decoded block and the number of bytes
21 // that the length header occupied.
22 func decodedLen(src []byte) (blockLen, headerLen int, err error) {
23 v, n := binary.Uvarint(src)
24 if n == 0 {
25 return 0, 0, ErrCorrupt
26 }
27 if uint64(int(v)) != v {
28 return 0, 0, errors.New("snappy: decoded block is too large")
29 }
30 return int(v), n, nil
31 }
32
33 // Decode returns the decoded form of src. The returned slice may be a sub-
34 // slice of dst if dst was large enough to hold the entire decoded block.
35 // Otherwise, a newly allocated slice will be returned.
36 // It is valid to pass a nil dst.
37 func Decode(dst, src []byte) ([]byte, error) {
38 dLen, s, err := decodedLen(src)
39 if err != nil {
40 return nil, err
41 }
42 if len(dst) < dLen {
43 dst = make([]byte, dLen)
44 }
45
46 var d, offset, length int
47 for s < len(src) {
48 switch src[s] & 0x03 {
49 case tagLiteral:
50 x := uint(src[s] >> 2)
51 switch {
52 case x < 60:
53 s += 1
54 case x == 60:
55 s += 2
56 if s > len(src) {
57 return nil, ErrCorrupt
58 }
59 x = uint(src[s-1])
60 case x == 61:
61 s += 3
62 if s > len(src) {
63 return nil, ErrCorrupt
64 }
65 x = uint(src[s-2]) | uint(src[s-1])<<8
66 case x == 62:
67 s += 4
68 if s > len(src) {
69 return nil, ErrCorrupt
70 }
71 x = uint(src[s-3]) | uint(src[s-2])<<8 | uint(src[s-1])<<16
72 case x == 63:
73 s += 5
74 if s > len(src) {
75 return nil, ErrCorrupt
76 }
77 x = uint(src[s-4]) | uint(src[s-3])<<8 | uint(src[s-2])<<16 | uint(src[s-1])<<24
78 }
79 length = int(x + 1)
80 if length <= 0 {
81 return nil, errors.New("snappy: unsupported literal length")
82 }
83 if length > len(dst)-d || length > len(src)-s {
84 return nil, ErrCorrupt
85 }
86 copy(dst[d:], src[s:s+length])
87 d += length
88 s += length
89 continue
90
91 case tagCopy1:
92 s += 2
93 if s > len(src) {
94 return nil, ErrCorrupt
95 }
96 length = 4 + int(src[s-2])>>2&0x7
97 offset = int(src[s-2])&0xe0<<3 | int(src[s-1])
98
99 case tagCopy2:
100 s += 3
101 if s > len(src) {
102 return nil, ErrCorrupt
103 }
104 length = 1 + int(src[s-3])>>2
105 offset = int(src[s-2]) | int(src[s-1])<<8
106
107 case tagCopy4:
108 return nil, errors.New("snappy: unsupported COPY_4 tag")
109 }
110
111 end := d + length
112 if offset > d || end > len(dst) {
113 return nil, ErrCorrupt
114 }
115 for ; d < end; d++ {
116 dst[d] = dst[d-offset]
117 }
118 }
119 if d != dLen {
120 return nil, ErrCorrupt
121 }
122 return dst[:d], nil
123 }
0 // Copyright 2011 The Snappy-Go Authors. All rights reserved.
1 // Use of this source code is governed by a BSD-style
2 // license that can be found in the LICENSE file.
3
4 package snappy
5
6 import (
7 "encoding/binary"
8 )
9
10 // We limit how far copy back-references can go, the same as the C++ code.
11 const maxOffset = 1 << 15
12
13 // emitLiteral writes a literal chunk and returns the number of bytes written.
14 func emitLiteral(dst, lit []byte) int {
15 i, n := 0, uint(len(lit)-1)
16 switch {
17 case n < 60:
18 dst[0] = uint8(n)<<2 | tagLiteral
19 i = 1
20 case n < 1<<8:
21 dst[0] = 60<<2 | tagLiteral
22 dst[1] = uint8(n)
23 i = 2
24 case n < 1<<16:
25 dst[0] = 61<<2 | tagLiteral
26 dst[1] = uint8(n)
27 dst[2] = uint8(n >> 8)
28 i = 3
29 case n < 1<<24:
30 dst[0] = 62<<2 | tagLiteral
31 dst[1] = uint8(n)
32 dst[2] = uint8(n >> 8)
33 dst[3] = uint8(n >> 16)
34 i = 4
35 case int64(n) < 1<<32:
36 dst[0] = 63<<2 | tagLiteral
37 dst[1] = uint8(n)
38 dst[2] = uint8(n >> 8)
39 dst[3] = uint8(n >> 16)
40 dst[4] = uint8(n >> 24)
41 i = 5
42 default:
43 panic("snappy: source buffer is too long")
44 }
45 if copy(dst[i:], lit) != len(lit) {
46 panic("snappy: destination buffer is too short")
47 }
48 return i + len(lit)
49 }
50
51 // emitCopy writes a copy chunk and returns the number of bytes written.
52 func emitCopy(dst []byte, offset, length int) int {
53 i := 0
54 for length > 0 {
55 x := length - 4
56 if 0 <= x && x < 1<<3 && offset < 1<<11 {
57 dst[i+0] = uint8(offset>>8)&0x07<<5 | uint8(x)<<2 | tagCopy1
58 dst[i+1] = uint8(offset)
59 i += 2
60 break
61 }
62
63 x = length
64 if x > 1<<6 {
65 x = 1 << 6
66 }
67 dst[i+0] = uint8(x-1)<<2 | tagCopy2
68 dst[i+1] = uint8(offset)
69 dst[i+2] = uint8(offset >> 8)
70 i += 3
71 length -= x
72 }
73 return i
74 }
75
76 // Encode returns the encoded form of src. The returned slice may be a sub-
77 // slice of dst if dst was large enough to hold the entire encoded block.
78 // Otherwise, a newly allocated slice will be returned.
79 // It is valid to pass a nil dst.
80 func Encode(dst, src []byte) ([]byte, error) {
81 if n := MaxEncodedLen(len(src)); len(dst) < n {
82 dst = make([]byte, n)
83 }
84
85 // The block starts with the varint-encoded length of the decompressed bytes.
86 d := binary.PutUvarint(dst, uint64(len(src)))
87
88 // Return early if src is short.
89 if len(src) <= 4 {
90 if len(src) != 0 {
91 d += emitLiteral(dst[d:], src)
92 }
93 return dst[:d], nil
94 }
95
96 // Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
97 const maxTableSize = 1 << 14
98 shift, tableSize := uint(32-8), 1<<8
99 for tableSize < maxTableSize && tableSize < len(src) {
100 shift--
101 tableSize *= 2
102 }
103 var table [maxTableSize]int
104
105 // Iterate over the source bytes.
106 var (
107 s int // The iterator position.
108 t int // The last position with the same hash as s.
109 lit int // The start position of any pending literal bytes.
110 )
111 for s+3 < len(src) {
112 // Update the hash table.
113 b0, b1, b2, b3 := src[s], src[s+1], src[s+2], src[s+3]
114 h := uint32(b0) | uint32(b1)<<8 | uint32(b2)<<16 | uint32(b3)<<24
115 p := &table[(h*0x1e35a7bd)>>shift]
116 // We need to to store values in [-1, inf) in table. To save
117 // some initialization time, (re)use the table's zero value
118 // and shift the values against this zero: add 1 on writes,
119 // subtract 1 on reads.
120 t, *p = *p-1, s+1
121 // If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte.
122 if t < 0 || s-t >= maxOffset || b0 != src[t] || b1 != src[t+1] || b2 != src[t+2] || b3 != src[t+3] {
123 s++
124 continue
125 }
126 // Otherwise, we have a match. First, emit any pending literal bytes.
127 if lit != s {
128 d += emitLiteral(dst[d:], src[lit:s])
129 }
130 // Extend the match to be as long as possible.
131 s0 := s
132 s, t = s+4, t+4
133 for s < len(src) && src[s] == src[t] {
134 s++
135 t++
136 }
137 // Emit the copied bytes.
138 d += emitCopy(dst[d:], s-t, s-s0)
139 lit = s
140 }
141
142 // Emit any final pending literal bytes and return.
143 if lit != len(src) {
144 d += emitLiteral(dst[d:], src[lit:])
145 }
146 return dst[:d], nil
147 }
148
149 // MaxEncodedLen returns the maximum length of a snappy block, given its
150 // uncompressed length.
151 func MaxEncodedLen(srcLen int) int {
152 // Compressed data can be defined as:
153 // compressed := item* literal*
154 // item := literal* copy
155 //
156 // The trailing literal sequence has a space blowup of at most 62/60
157 // since a literal of length 60 needs one tag byte + one extra byte
158 // for length information.
159 //
160 // Item blowup is trickier to measure. Suppose the "copy" op copies
161 // 4 bytes of data. Because of a special check in the encoding code,
162 // we produce a 4-byte copy only if the offset is < 65536. Therefore
163 // the copy op takes 3 bytes to encode, and this type of item leads
164 // to at most the 62/60 blowup for representing literals.
165 //
166 // Suppose the "copy" op copies 5 bytes of data. If the offset is big
167 // enough, it will take 5 bytes to encode the copy op. Therefore the
168 // worst case here is a one-byte literal followed by a five-byte copy.
169 // That is, 6 bytes of input turn into 7 bytes of "compressed" data.
170 //
171 // This last factor dominates the blowup, so the final estimate is:
172 return 32 + srcLen + srcLen/6
173 }
0 // Copyright 2011 The Snappy-Go Authors. All rights reserved.
1 // Use of this source code is governed by a BSD-style
2 // license that can be found in the LICENSE file.
3
4 // Package snappy implements the snappy block-based compression format.
5 // It aims for very high speeds and reasonable compression.
6 //
7 // The C++ snappy implementation is at http://code.google.com/p/snappy/
8 package snappy
9
10 /*
11 Each encoded block begins with the varint-encoded length of the decoded data,
12 followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
13 first byte of each chunk is broken into its 2 least and 6 most significant bits
14 called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag.
15 Zero means a literal tag. All other values mean a copy tag.
16
17 For literal tags:
18 - If m < 60, the next 1 + m bytes are literal bytes.
19 - Otherwise, let n be the little-endian unsigned integer denoted by the next
20 m - 59 bytes. The next 1 + n bytes after that are literal bytes.
21
22 For copy tags, length bytes are copied from offset bytes ago, in the style of
23 Lempel-Ziv compression algorithms. In particular:
24 - For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12).
25 The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10
26 of the offset. The next byte is bits 0-7 of the offset.
27 - For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
28 The length is 1 + m. The offset is the little-endian unsigned integer
29 denoted by the next 2 bytes.
30 - For l == 3, this tag is a legacy format that is no longer supported.
31 */
32 const (
33 tagLiteral = 0x00
34 tagCopy1 = 0x01
35 tagCopy2 = 0x02
36 tagCopy4 = 0x03
37 )
0 // Copyright 2011 The Snappy-Go Authors. All rights reserved.
1 // Use of this source code is governed by a BSD-style
2 // license that can be found in the LICENSE file.
3
4 package snappy
5
6 import (
7 "bytes"
8 "flag"
9 "fmt"
10 "io"
11 "io/ioutil"
12 "math/rand"
13 "net/http"
14 "os"
15 "path/filepath"
16 "strings"
17 "testing"
18 )
19
20 var download = flag.Bool("download", false, "If true, download any missing files before running benchmarks")
21
22 func roundtrip(b, ebuf, dbuf []byte) error {
23 e, err := Encode(ebuf, b)
24 if err != nil {
25 return fmt.Errorf("encoding error: %v", err)
26 }
27 d, err := Decode(dbuf, e)
28 if err != nil {
29 return fmt.Errorf("decoding error: %v", err)
30 }
31 if !bytes.Equal(b, d) {
32 return fmt.Errorf("roundtrip mismatch:\n\twant %v\n\tgot %v", b, d)
33 }
34 return nil
35 }
36
37 func TestEmpty(t *testing.T) {
38 if err := roundtrip(nil, nil, nil); err != nil {
39 t.Fatal(err)
40 }
41 }
42
43 func TestSmallCopy(t *testing.T) {
44 for _, ebuf := range [][]byte{nil, make([]byte, 20), make([]byte, 64)} {
45 for _, dbuf := range [][]byte{nil, make([]byte, 20), make([]byte, 64)} {
46 for i := 0; i < 32; i++ {
47 s := "aaaa" + strings.Repeat("b", i) + "aaaabbbb"
48 if err := roundtrip([]byte(s), ebuf, dbuf); err != nil {
49 t.Errorf("len(ebuf)=%d, len(dbuf)=%d, i=%d: %v", len(ebuf), len(dbuf), i, err)
50 }
51 }
52 }
53 }
54 }
55
56 func TestSmallRand(t *testing.T) {
57 rand.Seed(27354294)
58 for n := 1; n < 20000; n += 23 {
59 b := make([]byte, n)
60 for i, _ := range b {
61 b[i] = uint8(rand.Uint32())
62 }
63 if err := roundtrip(b, nil, nil); err != nil {
64 t.Fatal(err)
65 }
66 }
67 }
68
69 func TestSmallRegular(t *testing.T) {
70 for n := 1; n < 20000; n += 23 {
71 b := make([]byte, n)
72 for i, _ := range b {
73 b[i] = uint8(i%10 + 'a')
74 }
75 if err := roundtrip(b, nil, nil); err != nil {
76 t.Fatal(err)
77 }
78 }
79 }
80
81 func benchDecode(b *testing.B, src []byte) {
82 encoded, err := Encode(nil, src)
83 if err != nil {
84 b.Fatal(err)
85 }
86 // Bandwidth is in amount of uncompressed data.
87 b.SetBytes(int64(len(src)))
88 b.ResetTimer()
89 for i := 0; i < b.N; i++ {
90 Decode(src, encoded)
91 }
92 }
93
94 func benchEncode(b *testing.B, src []byte) {
95 // Bandwidth is in amount of uncompressed data.
96 b.SetBytes(int64(len(src)))
97 dst := make([]byte, MaxEncodedLen(len(src)))
98 b.ResetTimer()
99 for i := 0; i < b.N; i++ {
100 Encode(dst, src)
101 }
102 }
103
104 func readFile(b *testing.B, filename string) []byte {
105 src, err := ioutil.ReadFile(filename)
106 if err != nil {
107 b.Fatalf("failed reading %s: %s", filename, err)
108 }
109 if len(src) == 0 {
110 b.Fatalf("%s has zero length", filename)
111 }
112 return src
113 }
114
115 // expand returns a slice of length n containing repeated copies of src.
116 func expand(src []byte, n int) []byte {
117 dst := make([]byte, n)
118 for x := dst; len(x) > 0; {
119 i := copy(x, src)
120 x = x[i:]
121 }
122 return dst
123 }
124
125 func benchWords(b *testing.B, n int, decode bool) {
126 // Note: the file is OS-language dependent so the resulting values are not
127 // directly comparable for non-US-English OS installations.
128 data := expand(readFile(b, "/usr/share/dict/words"), n)
129 if decode {
130 benchDecode(b, data)
131 } else {
132 benchEncode(b, data)
133 }
134 }
135
136 func BenchmarkWordsDecode1e3(b *testing.B) { benchWords(b, 1e3, true) }
137 func BenchmarkWordsDecode1e4(b *testing.B) { benchWords(b, 1e4, true) }
138 func BenchmarkWordsDecode1e5(b *testing.B) { benchWords(b, 1e5, true) }
139 func BenchmarkWordsDecode1e6(b *testing.B) { benchWords(b, 1e6, true) }
140 func BenchmarkWordsEncode1e3(b *testing.B) { benchWords(b, 1e3, false) }
141 func BenchmarkWordsEncode1e4(b *testing.B) { benchWords(b, 1e4, false) }
142 func BenchmarkWordsEncode1e5(b *testing.B) { benchWords(b, 1e5, false) }
143 func BenchmarkWordsEncode1e6(b *testing.B) { benchWords(b, 1e6, false) }
144
145 // testFiles' values are copied directly from
146 // https://code.google.com/p/snappy/source/browse/trunk/snappy_unittest.cc.
147 // The label field is unused in snappy-go.
148 var testFiles = []struct {
149 label string
150 filename string
151 }{
152 {"html", "html"},
153 {"urls", "urls.10K"},
154 {"jpg", "house.jpg"},
155 {"pdf", "mapreduce-osdi-1.pdf"},
156 {"html4", "html_x_4"},
157 {"cp", "cp.html"},
158 {"c", "fields.c"},
159 {"lsp", "grammar.lsp"},
160 {"xls", "kennedy.xls"},
161 {"txt1", "alice29.txt"},
162 {"txt2", "asyoulik.txt"},
163 {"txt3", "lcet10.txt"},
164 {"txt4", "plrabn12.txt"},
165 {"bin", "ptt5"},
166 {"sum", "sum"},
167 {"man", "xargs.1"},
168 {"pb", "geo.protodata"},
169 {"gaviota", "kppkn.gtb"},
170 }
171
172 // The test data files are present at this canonical URL.
173 const baseURL = "https://snappy.googlecode.com/svn/trunk/testdata/"
174
175 func downloadTestdata(basename string) (errRet error) {
176 filename := filepath.Join("testdata", basename)
177 f, err := os.Create(filename)
178 if err != nil {
179 return fmt.Errorf("failed to create %s: %s", filename, err)
180 }
181 defer f.Close()
182 defer func() {
183 if errRet != nil {
184 os.Remove(filename)
185 }
186 }()
187 resp, err := http.Get(baseURL + basename)
188 if err != nil {
189 return fmt.Errorf("failed to download %s: %s", baseURL+basename, err)
190 }
191 defer resp.Body.Close()
192 _, err = io.Copy(f, resp.Body)
193 if err != nil {
194 return fmt.Errorf("failed to write %s: %s", filename, err)
195 }
196 return nil
197 }
198
199 func benchFile(b *testing.B, n int, decode bool) {
200 filename := filepath.Join("testdata", testFiles[n].filename)
201 if stat, err := os.Stat(filename); err != nil || stat.Size() == 0 {
202 if !*download {
203 b.Fatal("test data not found; skipping benchmark without the -download flag")
204 }
205 // Download the official snappy C++ implementation reference test data
206 // files for benchmarking.
207 if err := os.Mkdir("testdata", 0777); err != nil && !os.IsExist(err) {
208 b.Fatalf("failed to create testdata: %s", err)
209 }
210 for _, tf := range testFiles {
211 if err := downloadTestdata(tf.filename); err != nil {
212 b.Fatalf("failed to download testdata: %s", err)
213 }
214 }
215 }
216 data := readFile(b, filename)
217 if decode {
218 benchDecode(b, data)
219 } else {
220 benchEncode(b, data)
221 }
222 }
223
224 // Naming convention is kept similar to what snappy's C++ implementation uses.
225 func Benchmark_UFlat0(b *testing.B) { benchFile(b, 0, true) }
226 func Benchmark_UFlat1(b *testing.B) { benchFile(b, 1, true) }
227 func Benchmark_UFlat2(b *testing.B) { benchFile(b, 2, true) }
228 func Benchmark_UFlat3(b *testing.B) { benchFile(b, 3, true) }
229 func Benchmark_UFlat4(b *testing.B) { benchFile(b, 4, true) }
230 func Benchmark_UFlat5(b *testing.B) { benchFile(b, 5, true) }
231 func Benchmark_UFlat6(b *testing.B) { benchFile(b, 6, true) }
232 func Benchmark_UFlat7(b *testing.B) { benchFile(b, 7, true) }
233 func Benchmark_UFlat8(b *testing.B) { benchFile(b, 8, true) }
234 func Benchmark_UFlat9(b *testing.B) { benchFile(b, 9, true) }
235 func Benchmark_UFlat10(b *testing.B) { benchFile(b, 10, true) }
236 func Benchmark_UFlat11(b *testing.B) { benchFile(b, 11, true) }
237 func Benchmark_UFlat12(b *testing.B) { benchFile(b, 12, true) }
238 func Benchmark_UFlat13(b *testing.B) { benchFile(b, 13, true) }
239 func Benchmark_UFlat14(b *testing.B) { benchFile(b, 14, true) }
240 func Benchmark_UFlat15(b *testing.B) { benchFile(b, 15, true) }
241 func Benchmark_UFlat16(b *testing.B) { benchFile(b, 16, true) }
242 func Benchmark_UFlat17(b *testing.B) { benchFile(b, 17, true) }
243 func Benchmark_ZFlat0(b *testing.B) { benchFile(b, 0, false) }
244 func Benchmark_ZFlat1(b *testing.B) { benchFile(b, 1, false) }
245 func Benchmark_ZFlat2(b *testing.B) { benchFile(b, 2, false) }
246 func Benchmark_ZFlat3(b *testing.B) { benchFile(b, 3, false) }
247 func Benchmark_ZFlat4(b *testing.B) { benchFile(b, 4, false) }
248 func Benchmark_ZFlat5(b *testing.B) { benchFile(b, 5, false) }
249 func Benchmark_ZFlat6(b *testing.B) { benchFile(b, 6, false) }
250 func Benchmark_ZFlat7(b *testing.B) { benchFile(b, 7, false) }
251 func Benchmark_ZFlat8(b *testing.B) { benchFile(b, 8, false) }
252 func Benchmark_ZFlat9(b *testing.B) { benchFile(b, 9, false) }
253 func Benchmark_ZFlat10(b *testing.B) { benchFile(b, 10, false) }
254 func Benchmark_ZFlat11(b *testing.B) { benchFile(b, 11, false) }
255 func Benchmark_ZFlat12(b *testing.B) { benchFile(b, 12, false) }
256 func Benchmark_ZFlat13(b *testing.B) { benchFile(b, 13, false) }
257 func Benchmark_ZFlat14(b *testing.B) { benchFile(b, 14, false) }
258 func Benchmark_ZFlat15(b *testing.B) { benchFile(b, 15, false) }
259 func Benchmark_ZFlat16(b *testing.B) { benchFile(b, 16, false) }
260 func Benchmark_ZFlat17(b *testing.B) { benchFile(b, 17, false) }
0 // snappystream wraps snappy-go and supplies a Reader and Writer
1 // for the snappy framed stream format:
2 // https://snappy.googlecode.com/svn/trunk/framing_format.txt
3 package snappystream
4
5 import (
6 "hash/crc32"
7
8 "github.com/mreiferson/go-snappystream/snappy-go"
9 )
10
11 // Ext is the file extension for files whose content is a snappy framed stream.
12 const Ext = ".sz"
13
14 // MediaType is the MIME type used to represent snappy framed content.
15 const MediaType = "application/x-snappy-framed"
16
17 // ContentEncoding is the appropriate HTTP Content-Encoding header value for
18 // requests containing a snappy framed entity body.
19 const ContentEncoding = "x-snappy-framed"
20
21 // MaxBlockSize is the maximum number of decoded bytes allowed to be
22 // represented in a snappy framed block (sections 4.2 and 4.3).
23 const MaxBlockSize = 65536
24
25 // maxEncodedBlockSize is the maximum number of encoded bytes in a framed
26 // block.
27 var maxEncodedBlockSize = uint32(snappy.MaxEncodedLen(MaxBlockSize))
28
29 const VerifyChecksum = true
30 const SkipVerifyChecksum = false
31
32 // Block types defined by the snappy framed format specification.
33 const (
34 blockCompressed = 0x00
35 blockUncompressed = 0x01
36 blockPadding = 0xfe
37 blockStreamIdentifier = 0xff
38 )
39
40 // streamID is the stream identifier block that begins a valid snappy framed
41 // stream.
42 var streamID = []byte{0xff, 0x06, 0x00, 0x00, 0x73, 0x4e, 0x61, 0x50, 0x70, 0x59}
43
44 // maskChecksum implements the checksum masking algorithm described by the spec.
45 func maskChecksum(c uint32) uint32 {
46 return ((c >> 15) | (c << 17)) + 0xa282ead8
47 }
48
49 var crcTable *crc32.Table
50
51 func init() {
52 crcTable = crc32.MakeTable(crc32.Castagnoli)
53 }
0 package snappystream
1
2 import (
3 "bufio"
4 "errors"
5 "fmt"
6 "hash/crc32"
7 "io"
8
9 "github.com/mreiferson/go-snappystream/snappy-go"
10 )
11
12 var errClosed = fmt.Errorf("closed")
13
14 // BufferedWriter is an io.WriteCloser with behavior similar to writers
15 // returned by NewWriter but it buffers written data, maximizing block size (to
16 // improve the output compression ratio) at the cost of speed. Benefits over
17 // NewWriter are most noticible when individual writes are small and when
18 // streams are long.
19 //
20 // Failure to call a BufferedWriter's Close or Flush methods after it is done
21 // being written to will likely result in missing data frames which will be
22 // undetectable in the decoding process.
23 //
24 // NOTE: BufferedWriter cannot be instantiated via struct literal and must
25 // use NewBufferedWriter (i.e. its zero value is not usable).
26 type BufferedWriter struct {
27 err error
28 w *writer
29 bw *bufio.Writer
30 }
31
32 // NewBufferedWriter allocates and returns a BufferedWriter with an internal
33 // buffer of MaxBlockSize bytes. If an error occurs writing a block to w, all
34 // future writes will fail with the same error. After all data has been
35 // written, the client should call the Flush method to guarantee all data has
36 // been forwarded to the underlying io.Writer.
37 func NewBufferedWriter(w io.Writer) *BufferedWriter {
38 _w := NewWriter(w).(*writer)
39 return &BufferedWriter{
40 w: _w,
41 bw: bufio.NewWriterSize(_w, MaxBlockSize),
42 }
43 }
44
45 // ReadFrom implements the io.ReaderFrom interface used by io.Copy. It encodes
46 // data read from r as a snappy framed stream that is written to the underlying
47 // writer. ReadFrom returns the number number of bytes read, along with any
48 // error encountered (other than io.EOF).
49 func (w *BufferedWriter) ReadFrom(r io.Reader) (int64, error) {
50 if w.err != nil {
51 return 0, w.err
52 }
53
54 var n int64
55 n, w.err = w.bw.ReadFrom(r)
56 return n, w.err
57 }
58
59 // Write buffers p internally, encoding and writing a block to the underlying
60 // buffer if the buffer grows beyond MaxBlockSize bytes. The returned int
61 // will be 0 if there was an error and len(p) otherwise.
62 func (w *BufferedWriter) Write(p []byte) (int, error) {
63 if w.err != nil {
64 return 0, w.err
65 }
66
67 _, w.err = w.bw.Write(p)
68 if w.err != nil {
69 return 0, w.err
70 }
71
72 return len(p), nil
73 }
74
75 // Flush encodes and writes a block with the contents of w's internal buffer to
76 // the underlying writer even if the buffer does not contain a full block of
77 // data (MaxBlockSize bytes).
78 func (w *BufferedWriter) Flush() error {
79 if w.err == nil {
80 w.err = w.bw.Flush()
81 }
82
83 return w.err
84 }
85
86 // Close flushes w's internal buffer and tears down internal data structures.
87 // After a successful call to Close method calls on w return an error. Close
88 // makes no attempt to close the underlying writer.
89 func (w *BufferedWriter) Close() error {
90 if w.err != nil {
91 return w.err
92 }
93
94 w.err = w.bw.Flush()
95 w.w = nil
96 w.bw = nil
97
98 if w.err != nil {
99 return w.err
100 }
101
102 w.err = errClosed
103 return nil
104 }
105
106 type writer struct {
107 writer io.Writer
108 err error
109
110 hdr []byte
111 dst []byte
112
113 sentStreamID bool
114 }
115
116 // NewWriter returns an io.Writer that writes its input to an underlying
117 // io.Writer encoded as a snappy framed stream. A stream identifier block is
118 // written to w preceding the first data block. The returned writer will never
119 // emit a block with length in bytes greater than MaxBlockSize+4 nor one
120 // containing more than MaxBlockSize bytes of (uncompressed) data.
121 //
122 // For each Write, the returned length will only ever be len(p) or 0,
123 // regardless of the length of *compressed* bytes written to the wrapped
124 // io.Writer. If the returned length is 0 then error will be non-nil. If
125 // len(p) exceeds 65536, the slice will be automatically chunked into smaller
126 // blocks which are all emitted before the call returns.
127 func NewWriter(w io.Writer) io.Writer {
128 return &writer{
129 writer: w,
130
131 hdr: make([]byte, 8),
132 dst: make([]byte, 4096),
133 }
134 }
135
136 func (w *writer) Write(p []byte) (int, error) {
137 if w.err != nil {
138 return 0, w.err
139 }
140
141 total := 0
142 sz := MaxBlockSize
143 var n int
144 for i := 0; i < len(p); i += n {
145 if i+sz > len(p) {
146 sz = len(p) - i
147 }
148
149 n, w.err = w.write(p[i : i+sz])
150 if w.err != nil {
151 return 0, w.err
152 }
153 total += n
154 }
155 return total, nil
156 }
157
158 // write attempts to encode p as a block and write it to the underlying writer.
159 // The returned int may not equal p's length if compression below
160 // MaxBlockSize-4 could not be achieved.
161 func (w *writer) write(p []byte) (int, error) {
162 var err error
163
164 if len(p) > MaxBlockSize {
165 return 0, errors.New(fmt.Sprintf("block too large %d > %d", len(p), MaxBlockSize))
166 }
167
168 w.dst = w.dst[:cap(w.dst)] // Encode does dumb resize w/o context. reslice avoids alloc.
169 w.dst, err = snappy.Encode(w.dst, p)
170 if err != nil {
171 return 0, err
172 }
173 block := w.dst
174 n := len(p)
175 compressed := true
176
177 // check for data which is better left uncompressed. this is determined if
178 // the encoded content is longer than the source.
179 if len(w.dst) >= len(p) {
180 compressed = false
181 block = p[:n]
182 }
183
184 if !w.sentStreamID {
185 _, err := w.writer.Write(streamID)
186 if err != nil {
187 return 0, err
188 }
189 w.sentStreamID = true
190 }
191
192 // set the block type
193 if compressed {
194 writeHeader(w.hdr, blockCompressed, block, p[:n])
195 } else {
196 writeHeader(w.hdr, blockUncompressed, block, p[:n])
197 }
198
199 _, err = w.writer.Write(w.hdr)
200 if err != nil {
201 return 0, err
202 }
203
204 _, err = w.writer.Write(block)
205 if err != nil {
206 return 0, err
207 }
208
209 return n, nil
210 }
211
212 // writeHeader panics if len(hdr) is less than 8.
213 func writeHeader(hdr []byte, btype byte, enc, dec []byte) {
214 hdr[0] = btype
215
216 // 3 byte little endian length of encoded content
217 length := uint32(len(enc)) + 4 // +4 for checksum
218 hdr[1] = byte(length)
219 hdr[2] = byte(length >> 8)
220 hdr[3] = byte(length >> 16)
221
222 // 4 byte little endian CRC32 checksum of decoded content
223 checksum := maskChecksum(crc32.Checksum(dec, crcTable))
224 hdr[4] = byte(checksum)
225 hdr[5] = byte(checksum >> 8)
226 hdr[6] = byte(checksum >> 16)
227 hdr[7] = byte(checksum >> 24)
228 }
0 package snappystream
1
2 import (
3 "bytes"
4 "io/ioutil"
5 "log"
6 "testing"
7 )
8
9 // This test ensures that all BufferedWriter methods fail after Close has been
10 // called.
11 func TestBufferedWriterClose(t *testing.T) {
12 w := NewBufferedWriter(ioutil.Discard)
13 err := w.Close()
14 if err != nil {
15 log.Fatalf("closing empty BufferedWriter: %v", err)
16 }
17 err = w.Close()
18 if err == nil {
19 log.Fatalf("successful close after close")
20 }
21 err = w.Flush()
22 if err == nil {
23 log.Fatalf("successful flush after close")
24 }
25 _, err = w.Write([]byte("abc"))
26 if err == nil {
27 log.Fatalf("successful write after close")
28 }
29 }
30
31 // This test simply checks that buffering has an effect in a situation where it
32 // is know it should.
33 func TestBufferedWriter_compression(t *testing.T) {
34 p := []byte("hello snappystream!")
35 n := 10
36
37 var shortbuf bytes.Buffer
38 w := NewWriter(&shortbuf)
39 for i := 0; i < n; i++ {
40 n, err := w.Write(p)
41 if err != nil {
42 t.Fatalf("writer error: %v", err)
43 }
44 if n != len(p) {
45 t.Fatalf("short write: %d", n)
46 }
47 }
48
49 var buf bytes.Buffer
50 bw := NewBufferedWriter(&buf)
51 for i := 0; i < n; i++ {
52 n, err := bw.Write(p)
53 if err != nil {
54 t.Fatalf("buffered writer error: %v", err)
55 }
56 if n != len(p) {
57 t.Fatalf("short write: %d", n)
58 }
59 }
60 err := bw.Close()
61 if err != nil {
62 t.Fatalf("closing buffer: %v", err)
63 }
64
65 uncompressed := int64(n) * int64(len(p))
66 compressed := shortbuf.Len()
67 bufcompressed := buf.Len()
68
69 if compressed <= bufcompressed {
70 t.Fatalf("no benefit from buffering (%d <= %d)", shortbuf.Len(), buf.Len())
71 }
72
73 c := float64(uncompressed) / float64(compressed)
74 bufc := float64(uncompressed) / float64(bufcompressed)
75 improved := bufc / c
76
77 t.Logf("BufferedWriter compression ratio %g (%.03g factor improvement over %g)", bufc, improved, c)
78 }
79
80 // This tests ensures flushing after every write is equivalent to using
81 // NewWriter directly.
82 func TestBufferedWriterFlush(t *testing.T) {
83 p := []byte("hello snappystream!")
84 n := 10
85
86 var shortbuf bytes.Buffer
87 w := NewWriter(&shortbuf)
88 for i := 0; i < n; i++ {
89 n, err := w.Write(p)
90 if err != nil {
91 t.Fatalf("writer error: %v", err)
92 }
93 if n != len(p) {
94 t.Fatalf("short write: %d", n)
95 }
96 }
97
98 var buf bytes.Buffer
99 bw := NewBufferedWriter(&buf)
100 for i := 0; i < n; i++ {
101 n, err := bw.Write(p)
102 if err != nil {
103 t.Fatalf("buffered writer error: %v", err)
104 }
105 if n != len(p) {
106 t.Fatalf("short write: %d", n)
107 }
108 err = bw.Flush()
109 if err != nil {
110 t.Fatalf("flush: %v", err)
111 }
112 }
113 err := bw.Close()
114 if err != nil {
115 t.Fatalf("closing buffer: %v", err)
116 }
117
118 if shortbuf.Len() != buf.Len() {
119 t.Fatalf("unexpected size: %d != %d", shortbuf.Len(), buf.Len())
120 }
121
122 if !bytes.Equal(shortbuf.Bytes(), buf.Bytes()) {
123 t.Fatalf("unexpected bytes")
124 }
125 }