├── views ├── user.html ├── repo.html ├── org.html ├── opt_out.html ├── job.html ├── error.html ├── resumeOrgs.html ├── index.html └── resume.html ├── images ├── srt.png ├── loader.gif ├── ttab.png └── low_contrast_linen.png ├── config.ru ├── README.markdown ├── index.html ├── css ├── print.css └── resume.css └── js ├── mustache.js └── githubresume.js /views/user.html: -------------------------------------------------------------------------------- 1 |

{{full_name}}

2 |

Information about this user

3 | -------------------------------------------------------------------------------- /images/srt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/resume.github.com/master/images/srt.png -------------------------------------------------------------------------------- /images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/resume.github.com/master/images/loader.gif -------------------------------------------------------------------------------- /images/ttab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/resume.github.com/master/images/ttab.png -------------------------------------------------------------------------------- /images/low_contrast_linen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/resume.github.com/master/images/low_contrast_linen.png -------------------------------------------------------------------------------- /views/repo.html: -------------------------------------------------------------------------------- 1 | {{#repo}} 2 |
3 |

{{repo}}

4 |

{{language}}

5 |
6 |

{{description}}

7 | {{/repo}} 8 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | use Rack::Static, :urls => ["/css", "/images", "/js", "/views"], :root => "." 2 | run lambda { |env| [200, { 'Content-Type' => 'text/html' }, File.open('index.html', File::RDONLY)] } 3 | -------------------------------------------------------------------------------- /views/org.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

{{name}}

4 |

Member

5 |

{{now}}

6 |

If you would like more information about this organization, please visit the organization page on GitHub.

7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /views/opt_out.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

This user has not opted in to this unofficial GitHub résumé service.

6 |
7 |

If you would like to opt-in, simply go to our GitHub Project page and star the project.

8 |
9 |
10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /views/job.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | {{name}} 4 |

5 |

{{#language}}{{language}} - {{/language}}Creator & Owner

6 |

{{date}}

7 |

{{description}}

8 |

9 | This repository has {{watchers}} {{watchersLabel}} and {{forks}} {{forksLabel}}. 10 | If you would like more information about this repository and my 11 | contributed code, please visit 12 | the repo on 13 | GitHub. 14 |

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /views/error.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

6 | There was an issue accessing the requested profile to auto-generate this résumé. Access the user's profile directly. 7 |

8 |
9 | 10 |
11 | 12 |
13 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | #### A service that creates your resume based on your GitHub repos. 2 | 3 | Possible Usecases: 4 | 5 | * Boon for all the tech-savy bosses who want to have a **quick view** of person's git/github activity, _before the interview_ 6 | 7 | ### Opt-In 8 | 9 | GitHub Resume is now opt-in rather than opt-out! To make your resume visible 10 | just **star** this project, [resume/resume.github.com](https://github.com/resume/resume.github.com). 11 | That's all there is to it! 12 | 13 | ### For Developers 14 | 15 | To run the app in development mode: 16 | 17 | rackup config.ru 18 | 19 | (You must have Ruby and the rack gem installed.) 20 | 21 | 22 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/resume/resume.github.com/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 23 | 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | GitHub Résumé 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /css/print.css: -------------------------------------------------------------------------------- 1 | /* 2 | Print stylesheet for résumés 3 | */ 4 | 5 | 6 | /* =! Template */ 7 | 8 | #doc2 { 9 | width: 100%; 10 | } 11 | #inner { 12 | margin: 0 auto; 13 | padding: 10pt; 14 | border: 0; 15 | } 16 | #hd { 17 | margin-top: 0; 18 | } 19 | 20 | .enlarge { 21 | padding-right: 0; 22 | } 23 | 24 | .talent li { 25 | border: 0; 26 | } 27 | 28 | .org p, 29 | .job p { 30 | margin: 5pt 0 15pt; 31 | } 32 | 33 | #repositories { 34 | padding-bottom: 0; 35 | } 36 | 37 | #jobs h4 { 38 | margin-top: 10pt; 39 | } 40 | 41 | #jobs .job, 42 | #about { 43 | page-break-inside: avoid; 44 | } 45 | 46 | #bd, 47 | #about { 48 | margin-bottom: 0; 49 | } 50 | 51 | #ft { 52 | padding: 5pt 0; 53 | } 54 | 55 | /* --------- */ 56 | 57 | 58 | 59 | /* =! Font */ 60 | 61 | body { 62 | font-size: 10pt; 63 | } 64 | 65 | #hd h1 { 66 | font-size: 28pt; 67 | } 68 | #hd h2 { 69 | font-size: 14pt; 70 | } 71 | 72 | #bd h2, 73 | #profile .enlarge, 74 | #about .enlarge { 75 | font-size: 12pt; 76 | } 77 | 78 | /* --------- */ 79 | 80 | 81 | 82 | /* =! Colors */ 83 | 84 | body, 85 | h1, h2, h3, h4 { 86 | color: #000; 87 | } 88 | 89 | #inner { 90 | background: #fff; 91 | } 92 | 93 | /* --------- */ 94 | 95 | 96 | 97 | /* =! Links */ 98 | 99 | #profile a:after, 100 | #repositories a:after, 101 | #about a:after { 102 | content: " (" attr(href) ")"; 103 | font-style: italic; 104 | font-size: 10pt; 105 | } 106 | #profile #myblog:after, 107 | #jobs p a:after { 108 | content: none; 109 | } 110 | 111 | a { 112 | text-decoration: none; 113 | } 114 | 115 | #mylanguages a, 116 | #jobs p a { 117 | color: inherit; 118 | } 119 | 120 | #actions { 121 | visibility: hidden; 122 | } 123 | 124 | /* --------- */ -------------------------------------------------------------------------------- /views/resumeOrgs.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 |

{{name}}

9 |

Github organization

10 |
11 |
12 | 13 |
14 |
15 | {{#gravatar_id}} 16 | avatar 17 | {{/gravatar_id}} 18 | {{#email}}

{{email}}

{{/email}} 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 |
30 |

Github Profile

31 |
32 |
33 |

34 | We are a group of developers 35 | {{#location}} 36 | based in {{location}} 37 | {{/location}} 38 | {{#repos}} 39 | with {{repos}} public {{reposLabel}} 40 | {{/repos}} 41 | {{#followers}} and {{followers}} {{followersLabel}}{{/followers}}. 42 | We created this GitHub group in {{since}}{{#earlyAdopter}}, therefore we're early adopters,{{/earlyAdopter}}{{#website}} and you can find more information about us at {{website}}{{/website}}. 43 |

44 |

45 |
46 |
47 |
48 |
49 |

Languages

50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |

Our Popular Repositories

58 |
59 | 60 |
61 | Loading information... 62 |
63 |
64 | 65 |
66 |
67 |

About This Résumé

68 |
69 |
70 |

71 | This résumé is generated automatically using information from github. The repositories are 72 | ordered by popularity based on a very simple popularity heuristic that defines the popularity of a repository 73 | by its sum of watchers and forks. Do not hesitate to visit our github group's page 74 | for more information about our repositories and work. 75 |

76 |
77 |
78 |
79 |
80 |
81 | 82 |
83 |

{{name}} — {{#email}}{{email}} — {{/email}} https://github.com/{{username}}

84 |
85 | 86 |
87 | 88 |
89 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

6 | As a software startup owner I really enjoy when people send us their 7 | résumés and they include their github account so we can see tangible work they have done. 8 |

9 |


10 |

11 | After a tweet by John Resig 12 | I imagined that it may be nice for people to be able to generate their GitHub résumés. 13 |

14 |



15 | 16 |
17 |

18 | 19 | 20 |

21 |
22 | 23 |
24 | 25 |
26 |



27 |

See some popular users

28 |
29 | 37 |
38 |
39 | 46 |
47 |
48 |
49 | 50 |
51 |
52 |

53 |

Notes, Information and Future features

54 |


55 | This is the first version. I am planning on adding 56 | things as such as your most committed forks, most committed repositories and make the "My Popular Repositories" be built from 57 | your complete list of repositories. Feel free to fork the page if you want to help :-) 58 |

59 |

60 |
61 | 62 |
63 | 64 | 65 |
66 | 67 | 76 | -------------------------------------------------------------------------------- /views/resume.html: -------------------------------------------------------------------------------- 1 |
2 | Print 3 | Email 4 |
5 | 6 |
7 | 8 |
9 | 10 |
11 |
12 |
13 |

{{name}}

14 |

{{userStatus}}

15 |
16 | 17 |
18 |
19 | {{#gravatar_id}} 20 | avatar 21 | {{/gravatar_id}} 22 | {{#email}}

{{/email}} 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 |
34 |

GitHub Profile

35 |
36 |
37 |

38 | On GitHub {{#earlyAdopter}}as an early adopter {{/earlyAdopter}} since {{since}}, {{name}} is a developer 39 | {{#location}} 40 | based in {{location}} 41 | {{/location}} 42 | {{#repos}}with {{repos}} public {{reposLabel}}{{/repos}}{{^repos}}without any public repository for now{{/repos}}{{#followers}} and {{followers}} {{followersLabel}}{{/followers}}. 43 |

44 |
45 |
46 | {{#website}} 47 |
48 |
49 |

Website

50 |
51 |
52 | {{website}} 53 |
54 |
55 | {{/website}} 56 |
57 |
58 |

Languages

59 |
60 |
61 | 62 |
63 |
64 |
65 |
66 |

Popular Repositories

67 |
68 | 69 |
70 | Loading information... 71 |
72 |
73 | 74 |
75 |
76 |

Organizations

77 |
78 | 79 |
80 | Loading information... 81 |
82 |
83 | 84 |
85 |
86 |

About This Résumé

87 |
88 |
89 |

90 | This résumé is generated automatically using public information from the developer's GitHub account. The repositories are 91 | ordered by popularity based on a very simple popularity heuristic that defines the popularity of a repository 92 | by its sum of watchers and forks. Do not hesitate to visit {{name}}'s GitHub page 93 | for a complete work history. 94 |

95 |
96 |
97 |
98 |
99 |
100 | 101 |
102 |

{{name}} — {{#email}}{{email}} — {{/email}} {{#website}}{{website}} — {{/website}} https://github.com/{{username}}

103 |
104 | 105 |
106 | 107 |
108 | -------------------------------------------------------------------------------- /css/resume.css: -------------------------------------------------------------------------------- 1 | /* 2 | --------------------------------------------------------------------------------- 3 | STRIPPED DOWN RESUME TEMPLATE 4 | html resume 5 | 6 | v0.9: 5/28/09 7 | 8 | design and code by: thingsthatarebrown.com 9 | (matt brown) 10 | --------------------------------------------------------------------------------- 11 | */ 12 | 13 | .msg { 14 | padding: 10px; 15 | background: #222; 16 | position: relative; 17 | } 18 | 19 | .msg h1 { color: #fff } 20 | 21 | .msg a { 22 | margin-left: 20px; 23 | background: #408814; 24 | color: white; 25 | padding: 4px 8px; 26 | text-decoration: none; 27 | } 28 | 29 | .msg a:hover { background: #266400 } 30 | 31 | html { 32 | height: 100% ! important; 33 | background: url('../images/low_contrast_linen.png'); 34 | } 35 | 36 | /* //-- yui-grids style overrides -- */ 37 | 38 | body { 39 | font-family: Georgia, Garamond, "Times New Roman", Times, serif; 40 | color: #444; 41 | } 42 | 43 | .yui-gf { 44 | margin-bottom: 2em; 45 | padding-bottom: 2em; 46 | border-bottom: 1px solid #ccc; 47 | } 48 | 49 | .yui-gf div.first { width: 12.3% } 50 | 51 | .yui-gf .yui-u { width: 80.2% } 52 | 53 | /* //-- header, body, footer -- */ 54 | 55 | #hd { 56 | margin: 2.5em 0 3em 0; 57 | padding-bottom: 1.5em; 58 | border-bottom: 1px solid #ccc; 59 | } 60 | 61 | #hd h1 { 62 | font-size: 48px; 63 | text-transform: uppercase; 64 | letter-spacing: 3px; 65 | } 66 | 67 | #hd h2 { 68 | text-transform: uppercase; 69 | letter-spacing: 2px; 70 | } 71 | 72 | #bd, 73 | #ft { margin-bottom: 2em } 74 | 75 | #ft p { 76 | margin-bottom: 0; 77 | text-align: center; 78 | } 79 | 80 | /* //-- footer -- */ 81 | 82 | #ft { 83 | padding: 1em 0 5em 0; 84 | font-size: 92%; 85 | border-top: 1px solid #ccc; 86 | text-align: center; 87 | } 88 | 89 | /* //-- core typography and style -- */ 90 | 91 | h2 { font-size: 152% } 92 | 93 | h3, 94 | h4 { font-size: 122% } 95 | 96 | h1, 97 | h2, 98 | h3, 99 | h4 { color: #333 } 100 | 101 | p { 102 | font-size: 100%; 103 | line-height: 18px; 104 | } 105 | 106 | a { color: #990003 } 107 | 108 | a:hover { text-decoration: none } 109 | 110 | strong { font-weight: bold } 111 | 112 | li { 113 | line-height: 24px; 114 | border-bottom: 1px solid #ccc; 115 | } 116 | 117 | p.enlarge { 118 | font-size: 144%; 119 | line-height: 24px; 120 | } 121 | 122 | .contact-info { 123 | margin-top: 7px; 124 | text-align: right; 125 | font-size: 12px; 126 | position: relative; 127 | float: left; 128 | width: 100%; 129 | } 130 | 131 | .contact-info img { 132 | float: right; 133 | border: 1px solid #ccc; 134 | width: 140px; 135 | height: 140px; 136 | margin: -24px 0 14px; 137 | border-radius: 3px; 138 | } 139 | 140 | .contact-info a { 141 | position : relative; 142 | float: left; 143 | width: 100%; 144 | } 145 | 146 | .first h2 { font-style: italic } 147 | 148 | .last { border-bottom: 0 } 149 | 150 | /* //-- section styles -- */ 151 | 152 | #pdf { 153 | display: block; 154 | float: left; 155 | background: #666; 156 | color: #fff; 157 | padding: 6px 50px 6px 12px; 158 | margin-bottom: 6px; 159 | text-decoration: none; 160 | } 161 | 162 | #pdf:hover { background: #222 } 163 | 164 | .org, 165 | .job { 166 | position: relative; 167 | margin-bottom: 1em; 168 | padding-bottom: 1em; 169 | border-bottom: 1px solid #ccc; 170 | } 171 | 172 | .org h3, 173 | .job h3 { 174 | font-size: 100%; 175 | } 176 | 177 | .org h4, 178 | .job h4 { 179 | position: absolute; 180 | top: 0.35em; 181 | right: 0; 182 | } 183 | 184 | .org a, 185 | .job a { 186 | border: none; 187 | text-decoration: none; 188 | } 189 | 190 | .org p, 191 | .job p { 192 | margin: 0.75em 0 3em 0; 193 | font-size: 122%; 194 | } 195 | 196 | .last { border: none } 197 | 198 | .talent { 199 | width: 32%; 200 | float: left; 201 | } 202 | 203 | .talent h2 { margin-bottom: 6px } 204 | 205 | #srt-ttab { 206 | margin-bottom: 100px; 207 | text-align: center; 208 | } 209 | 210 | #srt-ttab img.last { margin-top: 20px } 211 | 212 | /* --// override to force 1/8th width grids -- */ 213 | 214 | #username { 215 | position: relative; 216 | float: left; 217 | height: 30px; 218 | width: 75%; 219 | border: 2px solid #444; 220 | -webkit-border-radius: 4px; 221 | -moz-border-radius: 4px; 222 | border-radius: 4px; 223 | font-family: Georgia, Garamond, "Times New Roman", Times, serif; 224 | color: #444; 225 | font-size: 18px; 226 | padding: 5px; 227 | } 228 | 229 | #gen { 230 | position: relative; 231 | float: left; 232 | margin-left: 10px; 233 | width: 20%; 234 | height: 44px; 235 | font-family: Georgia, Garamond, "Times New Roman", Times, serif; 236 | font-size: 18px; 237 | color: #fff; 238 | background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0.21, rgb(31,31,31)), color-stop(0.61, rgb(51,51,51)) ); 239 | background-image: -moz-linear-gradient( center bottom, rgb(31,31,31) 21%, rgb(51,51,51) 61% ); 240 | border: 1px solid #444; 241 | -webkit-border-radius: 4px; 242 | -moz-border-radius: 4px; 243 | border-radius: 4px; 244 | } 245 | 246 | #gen:hover { cursor: pointer } 247 | 248 | .yui-gb { 249 | margin-top: 10px; 250 | margin-bottom: 10px; 251 | padding-bottom: 10px; 252 | } 253 | 254 | .enlarge-medium { font-size: 15px } 255 | 256 | .yui-gb ul { width: 80% } 257 | 258 | noscript { 259 | display: block; 260 | margin-top: 30px; 261 | font-size: 152%; 262 | color: #990003; 263 | } 264 | 265 | #actions { 266 | padding: 30px 0 40px 0; 267 | margin: 0 auto 0 auto; 268 | width: 73.076em; 269 | } 270 | 271 | #actions a { 272 | color: #ccc; 273 | text-decoration: none; 274 | text-shadow: 0 0 5px #000; 275 | margin-left: 1em; 276 | } 277 | 278 | #actions * { float: right } 279 | 280 | #doc { 281 | margin-top: 70px; 282 | background: #f5f5f5; 283 | padding: 25px 107px 0px 107px; 284 | } 285 | 286 | #doc2 { padding-bottom: 70px } 287 | 288 | @media screen and (max-width: 59.9em) { 289 | #inner { 290 | padding: 5 5 5 5; 291 | margin: 0 auto; 292 | background: #f5f5f5; 293 | border: solid #666; 294 | border-width: 8px 0 2px 0; 295 | box-shadow: 0 1px 3px #000; 296 | } 297 | #hd h1 { 298 | font-size: 2.2em; 299 | font-weight: bold; 300 | text-transform: uppercase; 301 | letter-spacing: 0.2em; 302 | } 303 | 304 | #hd h2 { 305 | font-size: 1.5em; 306 | text-transform: uppercase; 307 | letter-spacing: 0.2em; 308 | } 309 | 310 | #doc2 { 311 | width: 100%; 312 | padding-bottom: 1em; 313 | } 314 | #actions{ 315 | display: none; 316 | } 317 | 318 | h1{ 319 | font-size: 2em; 320 | } 321 | h2{ 322 | font-size: 1.5em; 323 | } 324 | p.enlarge { 325 | font-size: 1.4em; 326 | line-height: 1.2em; 327 | } 328 | li { 329 | line-height: 1.6em; 330 | } 331 | h3, h4{ 332 | font-size: 1.3em; 333 | } 334 | p{ 335 | font-size: 1.2em; 336 | line-height: 1.1em; 337 | } 338 | .org p, .job p { 339 | margin: 0.75em 0 1.5em 0; 340 | } 341 | .first h2, .first h1{ 342 | padding-left: 0.5em; 343 | } 344 | 345 | .yui-gf div.first { 346 | width: 20%; 347 | } 348 | 349 | .yui-gf .yui-u { width: 80% } 350 | 351 | } 352 | 353 | @media screen and (min-width: 60em) { 354 | #inner { 355 | padding: 10px 80px; 356 | margin: 0 auto; 357 | background: #f5f5f5; 358 | border: solid #666; 359 | border-width: 8px 0 2px 0; 360 | box-shadow: 0 1px 3px #000; 361 | } 362 | } -------------------------------------------------------------------------------- /js/mustache.js: -------------------------------------------------------------------------------- 1 | /* 2 | mustache.js — Logic-less templates in JavaScript 3 | 4 | See http://mustache.github.com/ for more info. 5 | */ 6 | 7 | var Mustache = function() { 8 | var Renderer = function() {}; 9 | 10 | Renderer.prototype = { 11 | otag: "{{", 12 | ctag: "}}", 13 | pragmas: {}, 14 | buffer: [], 15 | pragmas_implemented: { 16 | "IMPLICIT-ITERATOR": true 17 | }, 18 | context: {}, 19 | 20 | render: function(template, context, partials, in_recursion) { 21 | // reset buffer & set context 22 | if(!in_recursion) { 23 | this.context = context; 24 | this.buffer = []; // TODO: make this non-lazy 25 | } 26 | 27 | // fail fast 28 | if(!this.includes("", template)) { 29 | if(in_recursion) { 30 | return template; 31 | } else { 32 | this.send(template); 33 | return; 34 | } 35 | } 36 | 37 | template = this.render_pragmas(template); 38 | var html = this.render_section(template, context, partials); 39 | if(in_recursion) { 40 | return this.render_tags(html, context, partials, in_recursion); 41 | } 42 | 43 | this.render_tags(html, context, partials, in_recursion); 44 | }, 45 | 46 | /* 47 | Sends parsed lines 48 | */ 49 | send: function(line) { 50 | if(line != "") { 51 | this.buffer.push(line); 52 | } 53 | }, 54 | 55 | /* 56 | Looks for %PRAGMAS 57 | */ 58 | render_pragmas: function(template) { 59 | // no pragmas 60 | if(!this.includes("%", template)) { 61 | return template; 62 | } 63 | 64 | var that = this; 65 | var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + 66 | this.ctag); 67 | return template.replace(regex, function(match, pragma, options) { 68 | if(!that.pragmas_implemented[pragma]) { 69 | throw({message: 70 | "This implementation of mustache doesn't understand the '" + 71 | pragma + "' pragma"}); 72 | } 73 | that.pragmas[pragma] = {}; 74 | if(options) { 75 | var opts = options.split("="); 76 | that.pragmas[pragma][opts[0]] = opts[1]; 77 | } 78 | return ""; 79 | // ignore unknown pragmas silently 80 | }); 81 | }, 82 | 83 | /* 84 | Tries to find a partial in the curent scope and render it 85 | */ 86 | render_partial: function(name, context, partials) { 87 | name = this.trim(name); 88 | if(!partials || partials[name] === undefined) { 89 | throw({message: "unknown_partial '" + name + "'"}); 90 | } 91 | if(typeof(context[name]) != "object") { 92 | return this.render(partials[name], context, partials, true); 93 | } 94 | return this.render(partials[name], context[name], partials, true); 95 | }, 96 | 97 | /* 98 | Renders inverted (^) and normal (#) sections 99 | */ 100 | render_section: function(template, context, partials) { 101 | if(!this.includes("#", template) && !this.includes("^", template)) { 102 | return template; 103 | } 104 | 105 | var that = this; 106 | // CSW - Added "+?" so it finds the tighest bound, not the widest 107 | var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag + 108 | "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + 109 | "\\s*", "mg"); 110 | 111 | // for each {{#foo}}{{/foo}} section do... 112 | return template.replace(regex, function(match, type, name, content) { 113 | var value = that.find(name, context); 114 | if(type == "^") { // inverted section 115 | if(!value || that.is_array(value) && value.length === 0) { 116 | // false or empty list, render it 117 | return that.render(content, context, partials, true); 118 | } else { 119 | return ""; 120 | } 121 | } else if(type == "#") { // normal section 122 | if(that.is_array(value)) { // Enumerable, Let's loop! 123 | return that.map(value, function(row) { 124 | return that.render(content, that.create_context(row), 125 | partials, true); 126 | }).join(""); 127 | } else if(that.is_object(value)) { // Object, Use it as subcontext! 128 | return that.render(content, that.create_context(value), 129 | partials, true); 130 | } else if(typeof value === "function") { 131 | // higher order section 132 | return value.call(context, content, function(text) { 133 | return that.render(text, context, partials, true); 134 | }); 135 | } else if(value) { // boolean section 136 | return that.render(content, context, partials, true); 137 | } else { 138 | return ""; 139 | } 140 | } 141 | }); 142 | }, 143 | 144 | /* 145 | Replace {{foo}} and friends with values from our view 146 | */ 147 | render_tags: function(template, context, partials, in_recursion) { 148 | // tit for tat 149 | var that = this; 150 | 151 | var new_regex = function() { 152 | return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + 153 | that.ctag + "+", "g"); 154 | }; 155 | 156 | var regex = new_regex(); 157 | var tag_replace_callback = function(match, operator, name) { 158 | switch(operator) { 159 | case "!": // ignore comments 160 | return ""; 161 | case "=": // set new delimiters, rebuild the replace regexp 162 | that.set_delimiters(name); 163 | regex = new_regex(); 164 | return ""; 165 | case ">": // render partial 166 | return that.render_partial(name, context, partials); 167 | case "{": // the triple mustache is unescaped 168 | return that.find(name, context); 169 | default: // escape the value 170 | return that.escape(that.find(name, context)); 171 | } 172 | }; 173 | var lines = template.split("\n"); 174 | for(var i = 0; i < lines.length; i++) { 175 | lines[i] = lines[i].replace(regex, tag_replace_callback, this); 176 | if(!in_recursion) { 177 | this.send(lines[i]); 178 | } 179 | } 180 | 181 | if(in_recursion) { 182 | return lines.join("\n"); 183 | } 184 | }, 185 | 186 | set_delimiters: function(delimiters) { 187 | var dels = delimiters.split(" "); 188 | this.otag = this.escape_regex(dels[0]); 189 | this.ctag = this.escape_regex(dels[1]); 190 | }, 191 | 192 | escape_regex: function(text) { 193 | // thank you Simon Willison 194 | if(!arguments.callee.sRE) { 195 | var specials = [ 196 | '/', '.', '*', '+', '?', '|', 197 | '(', ')', '[', ']', '{', '}', '\\' 198 | ]; 199 | arguments.callee.sRE = new RegExp( 200 | '(\\' + specials.join('|\\') + ')', 'g' 201 | ); 202 | } 203 | return text.replace(arguments.callee.sRE, '\\$1'); 204 | }, 205 | 206 | /* 207 | find `name` in current `context`. That is find me a value 208 | from the view object 209 | */ 210 | find: function(name, context) { 211 | name = this.trim(name); 212 | 213 | // Checks whether a value is thruthy or false or 0 214 | function is_kinda_truthy(bool) { 215 | return bool === false || bool === 0 || bool; 216 | } 217 | 218 | var value; 219 | if(is_kinda_truthy(context[name])) { 220 | value = context[name]; 221 | } else if(is_kinda_truthy(this.context[name])) { 222 | value = this.context[name]; 223 | } 224 | 225 | if(typeof value === "function") { 226 | return value.apply(context); 227 | } 228 | if(value !== undefined) { 229 | return value; 230 | } 231 | // silently ignore unkown variables 232 | return ""; 233 | }, 234 | 235 | // Utility methods 236 | 237 | /* includes tag */ 238 | includes: function(needle, haystack) { 239 | return haystack.indexOf(this.otag + needle) != -1; 240 | }, 241 | 242 | /* 243 | Does away with nasty characters 244 | */ 245 | escape: function(s) { 246 | s = String(s === null ? "" : s); 247 | return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) { 248 | switch(s) { 249 | case "&": return "&"; 250 | case "\\": return "\\\\"; 251 | case '"': return '\"'; 252 | case "<": return "<"; 253 | case ">": return ">"; 254 | default: return s; 255 | } 256 | }); 257 | }, 258 | 259 | // by @langalex, support for arrays of strings 260 | create_context: function(_context) { 261 | if(this.is_object(_context)) { 262 | return _context; 263 | } else { 264 | var iterator = "."; 265 | if(this.pragmas["IMPLICIT-ITERATOR"]) { 266 | iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; 267 | } 268 | var ctx = {}; 269 | ctx[iterator] = _context; 270 | return ctx; 271 | } 272 | }, 273 | 274 | is_object: function(a) { 275 | return a && typeof a == "object"; 276 | }, 277 | 278 | is_array: function(a) { 279 | return Object.prototype.toString.call(a) === '[object Array]'; 280 | }, 281 | 282 | /* 283 | Gets rid of leading and trailing whitespace 284 | */ 285 | trim: function(s) { 286 | return s.replace(/^\s*|\s*$/g, ""); 287 | }, 288 | 289 | /* 290 | Why, why, why? Because IE. Cry, cry cry. 291 | */ 292 | map: function(array, fn) { 293 | if (typeof array.map == "function") { 294 | return array.map(fn); 295 | } else { 296 | var r = []; 297 | var l = array.length; 298 | for(var i = 0; i < l; i++) { 299 | r.push(fn(array[i])); 300 | } 301 | return r; 302 | } 303 | } 304 | }; 305 | 306 | return({ 307 | name: "mustache.js", 308 | version: "0.3.0", 309 | 310 | /* 311 | Turns a template and view into HTML 312 | */ 313 | to_html: function(template, view, partials, send_fun) { 314 | var renderer = new Renderer(); 315 | if(send_fun) { 316 | renderer.send = send_fun; 317 | } 318 | renderer.render(template, view, partials); 319 | if(!send_fun) { 320 | return renderer.buffer.join("\n"); 321 | } 322 | } 323 | }); 324 | }(); -------------------------------------------------------------------------------- /js/githubresume.js: -------------------------------------------------------------------------------- 1 | var urlParams = {}; 2 | var username; 3 | var trackerId = 'UA-21222559-1'; 4 | 5 | (function () { 6 | var e, 7 | a = /\+/g, // Regex for replacing addition symbol with a space 8 | r = /([^&=]+)=?([^&]*)/g, 9 | d = function (s) { return decodeURIComponent(s.replace(a, " ")); }, 10 | q = window.location.search.substring(1); 11 | 12 | while (e = r.exec(q)) { 13 | urlParams[0] = d(e[1]); 14 | } 15 | })(); 16 | 17 | $(document).ready(function() { 18 | try { 19 | if (urlParams[0] !== undefined) { 20 | username = urlParams[0]; 21 | run(); 22 | } else { 23 | home(); 24 | } 25 | } catch (err) { 26 | try { 27 | console.log(err); 28 | } catch (e) { 29 | /*fail silently*/ 30 | } 31 | } 32 | }); 33 | 34 | var error = function() { 35 | $.ajax({ 36 | url: 'views/error.html', 37 | dataType: 'html', 38 | success: function(data) { 39 | var template = data; 40 | $('#resume').html(data); 41 | } 42 | }); 43 | }; 44 | 45 | var home = function() { 46 | $.ajax({ 47 | url: 'views/index.html', 48 | dataType: 'html', 49 | success: function(data) { 50 | var template = data; 51 | $('#resume').html(data); 52 | } 53 | }); 54 | }; 55 | 56 | var github_user = function(username, callback) { 57 | $.getJSON('https://api.github.com/users/' + username + '?callback=?', callback); 58 | } 59 | 60 | var github_user_repos = function(username, callback, page_number, prev_data) { 61 | var page = (page_number ? page_number : 1), 62 | url = 'https://api.github.com/users/' + username + '/repos?callback=?', 63 | data = (prev_data ? prev_data : []); 64 | 65 | if (page_number > 1) { 66 | url += '&page=' + page_number; 67 | } 68 | $.getJSON(url, function(repos) { 69 | data = data.concat(repos.data); 70 | if (repos.data.length > 0) { 71 | github_user_repos(username, callback, page + 1, data); 72 | } else { 73 | callback(data); 74 | } 75 | }); 76 | } 77 | 78 | var github_user_orgs = function(username, callback) { 79 | $.getJSON('https://api.github.com/users/' + username + '/orgs?callback=?', callback); 80 | } 81 | 82 | // Check to see if the user has starred the resume.github.com repo. 83 | // 84 | // Returns true/false. 85 | var github_user_starred_resume = function(username, page) { 86 | var star = false; 87 | var repos = []; 88 | var page = (page ? page : 1); 89 | var url = 'https://api.github.com/users/' + username + '/starred?page=' + page; 90 | 91 | $.ajax({ 92 | url: url, 93 | async: false, 94 | dataType: 'json', 95 | success: function(data) { 96 | repos = data; 97 | } 98 | }); 99 | 100 | $.each(repos, function(i, repo) { 101 | if (repo.full_name == "resume/resume.github.com") { 102 | star = true; 103 | return false; // stop iterating 104 | } 105 | }); 106 | 107 | if (star) { 108 | return star; 109 | } 110 | 111 | if (repos.length > 0) { 112 | star = github_user_starred_resume(username, page + 1); 113 | } 114 | 115 | return star; 116 | } 117 | 118 | var run = function() { 119 | var itemCount = 0, 120 | maxItems = 5, 121 | maxLanguages = 9; 122 | 123 | if (! github_user_starred_resume(username)) { 124 | $.ajax({ 125 | url: 'views/opt_out.html', 126 | dataType: 'html', 127 | success: function(data) { 128 | var template = data; 129 | $('#resume').html(data); 130 | } 131 | }); 132 | return; 133 | } 134 | 135 | var res = github_user(username, function(data) { 136 | data = data.data; 137 | var sinceDate = new Date(data.created_at); 138 | var sinceMonth = sinceDate.getMonth(); 139 | var since = sinceDate.getFullYear(); 140 | var sinceMonth = sinceDate.getMonth(); 141 | var currentYear = (new Date).getFullYear(); 142 | switch (since) { 143 | case currentYear-1: 144 | since = 'last year'; 145 | break; 146 | case currentYear: 147 | since = 'this year'; 148 | break; 149 | } 150 | 151 | var addHttp = ''; 152 | if (data.blog && data.blog.indexOf('http') < 0) { 153 | addHttp = 'http://'; 154 | } 155 | 156 | // set view.name to the "friendly" name e.g. "John Doe". If not defined 157 | // (in which case data.name is empty), fall back to the login 158 | // name e.g. "johndoe" 159 | var name = username; 160 | if (data.name !== null && data.name !== undefined && data.name.length) { 161 | name = data.name; 162 | } 163 | 164 | var avatar = ''; 165 | if (data.type == 'Organization'){ 166 | avatar = data.avatar_url.match(/https:\/\/secure.gravatar.com\/avatar\/[0-9a-z]+/)[0]; 167 | avatar += '?s=140&d=https://github.com/images/gravatars/gravatar-140.png'; 168 | } 169 | 170 | var view = { 171 | name: name, 172 | type: data.type, 173 | email: data.email, 174 | created_at: data.created_at, 175 | earlyAdopter: 0, 176 | location: data.location, 177 | gravatar_id: data.gravatar_id, 178 | avatar_url: avatar, 179 | repos: data.public_repos, 180 | reposLabel: data.public_repos > 1 ? 'repositories' : 'repository', 181 | followers: data.followers, 182 | followersLabel: data.followers > 1 ? 'followers' : 'follower', 183 | username: username, 184 | userStatus: 'GitHub user', 185 | since: since, 186 | resume_url: window.location 187 | }; 188 | 189 | // We consider a limit of 4 months since the GitHub opening (Feb 2008) to be considered as an early adopter 190 | if ((since == '2008' && sinceMonth <= 5) || since <= '2007') { 191 | view.earlyAdopter = 1; 192 | } 193 | 194 | view.userStatus = getUserStatus(); 195 | function getUserStatus() { 196 | var COEF_REPOS = 2; 197 | var COEF_GISTS = 0.25; 198 | var COEF_FOLLOWERS = 0.5; 199 | var COEF_FOLLOWING = 0.25; 200 | var FIRST_STEP = 0; 201 | var SECOND_STEP = 5; 202 | var THIRD_STEP = 20; 203 | var FOURTH_STEP = 50; 204 | var FIFTH_STEP = 150; 205 | var EXTRA_POINT_GAIN = 1; 206 | 207 | var statusScore = data.public_repos * COEF_REPOS 208 | + data.public_gists * COEF_GISTS 209 | + data.followers * COEF_FOLLOWERS 210 | + data.following * COEF_FOLLOWING; 211 | 212 | // Extra points 213 | // - Early adopter 214 | if (view.earlyAdopter == 1) { 215 | statusScore += EXTRA_POINT_GAIN; 216 | } 217 | // - Blog & Email & Location 218 | if (view.location && view.location != '' && view.email && view.email != '' && data.blog && data.blog != '') { 219 | statusScore += EXTRA_POINT_GAIN; 220 | } 221 | 222 | if (statusScore == FIRST_STEP) { 223 | return 'Inactive GitHub user'; 224 | } 225 | else if (statusScore > FIRST_STEP && statusScore <= SECOND_STEP) { 226 | return 'Newbie GitHub user'; 227 | } 228 | else if (statusScore > SECOND_STEP && statusScore <= THIRD_STEP) { 229 | return 'Regular GitHub user'; 230 | } 231 | else if (statusScore > THIRD_STEP && statusScore <= FOURTH_STEP) { 232 | return 'Advanced GitHub user'; 233 | } 234 | else if (statusScore > FOURTH_STEP && statusScore <= FIFTH_STEP) { 235 | return 'Enthusiastic GitHub user'; 236 | } 237 | else if (statusScore > FIFTH_STEP) { 238 | return 'Passionate GitHub user'; 239 | } 240 | }; 241 | 242 | if (data.blog !== undefined && data.blog !== null && data.blog !== '') { 243 | view.website = addHttp + data.blog; 244 | } 245 | 246 | var resume = (data.type == 'User' ? 'views/resume.html' : 'views/resumeOrgs.html'); 247 | $.ajax({ 248 | url: resume, 249 | dataType: 'html', 250 | success: function(data) { 251 | var template = data, 252 | html = Mustache.to_html(template, view); 253 | $('#resume').html(html); 254 | document.title = name + "'s Résumé"; 255 | $("#actions #print").click(function(){ 256 | window.print(); 257 | return false; 258 | }); 259 | } 260 | }); 261 | }); 262 | 263 | github_user_repos(username, function(data) { 264 | var sorted = [], 265 | languages = {}, 266 | popularity; 267 | 268 | $.each(data, function(i, repo) { 269 | if (repo.fork !== false) { 270 | return; 271 | } 272 | 273 | if (repo.language) { 274 | if (repo.language in languages) { 275 | languages[repo.language]++; 276 | } else { 277 | languages[repo.language] = 1; 278 | } 279 | } 280 | 281 | popularity = repo.watchers + repo.forks; 282 | sorted.push({position: i, popularity: popularity, info: repo}); 283 | }); 284 | 285 | function sortByPopularity(a, b) { 286 | return b.popularity - a.popularity; 287 | }; 288 | 289 | sorted.sort(sortByPopularity); 290 | 291 | var languageTotal = 0; 292 | function sortLanguages(languages, limit) { 293 | var sorted_languages = []; 294 | 295 | for (var lang in languages) { 296 | if (typeof(lang) !== "string") { 297 | continue; 298 | } 299 | sorted_languages.push({ 300 | name: lang, 301 | popularity: languages[lang], 302 | toString: function() { 303 | return '' + this.name + ''; 304 | } 305 | }); 306 | 307 | languageTotal += languages[lang]; 308 | } 309 | 310 | if (limit) { 311 | sorted_languages = sorted_languages.slice(0, limit); 312 | } 313 | 314 | return sorted_languages.sort(sortByPopularity); 315 | } 316 | 317 | $.ajax({ 318 | url: 'views/job.html', 319 | dataType: 'html', 320 | success: function(response) { 321 | languages = sortLanguages(languages, maxLanguages); 322 | 323 | if (languages && languages.length > 0) { 324 | var ul = $(''), 325 | percent, li; 326 | 327 | $.each(languages, function(i, lang) { 328 | x = i + 1; 329 | percent = parseInt((lang.popularity / languageTotal) * 100); 330 | li = $('
  • ' + lang.toString() + ' ('+percent+'%)
  • '); 331 | 332 | if (x % 3 == 0 || (languages.length < 3 && i == languages.length - 1)) { 333 | li.attr('class', 'last'); 334 | ul.append(li); 335 | $('#content-languages').append(ul); 336 | ul = $(''); 337 | } else { 338 | ul.append(li); 339 | $('#content-languages').append(ul); 340 | } 341 | }); 342 | } else { 343 | $('#mylanguages').hide(); 344 | } 345 | 346 | if (sorted.length > 0) { 347 | $('#jobs').html(''); 348 | itemCount = 0; 349 | var since, until, date, view, template, html; 350 | 351 | $.each(sorted, function(index, repo) { 352 | if (itemCount >= maxItems) { 353 | return; 354 | } 355 | 356 | since = new Date(repo.info.created_at); 357 | since = since.getFullYear(); 358 | until = new Date(repo.info.pushed_at); 359 | until = until.getFullYear(); 360 | if (since == until) { 361 | date = since; 362 | } else { 363 | date = since + ' - ' + until; 364 | } 365 | 366 | view = { 367 | name: repo.info.name, 368 | date: date, 369 | language: repo.info.language, 370 | description: repo.info.description, 371 | username: username, 372 | watchers: repo.info.watchers, 373 | forks: repo.info.forks, 374 | watchersLabel: repo.info.watchers == 0 || repo.info.watchers > 1 ? 'watchers' : 'watcher', 375 | forksLabel: repo.info.forks == 0 || repo.info.forks > 1 ? 'forks' : 'fork', 376 | }; 377 | 378 | if (itemCount == sorted.length - 1 || itemCount == maxItems - 1) { 379 | view.last = 'last'; 380 | } 381 | 382 | template = response; 383 | html = Mustache.to_html(template, view); 384 | 385 | $('#jobs').append($(html)); 386 | ++itemCount; 387 | }); 388 | } else { 389 | if(data.length > 0){ 390 | $('#jobs').html('').append('

    All of this user\'s repositories seem to be forks. Sorry.

    '); 391 | } else { 392 | $('#jobs').html('').append('

    Unfortunately, this user does not seem to have any public repositories.

    '); 393 | } 394 | } 395 | } 396 | }); 397 | }); 398 | 399 | github_user_orgs(username, function(response) { 400 | var sorted = []; 401 | 402 | $.each(response.data, function(i, org) { 403 | if (org.login === undefined) { 404 | return; 405 | } 406 | sorted.push({position: i, info: org}); 407 | }); 408 | 409 | $.ajax({ 410 | url: 'views/org.html', 411 | dataType: 'html', 412 | success: function(response) { 413 | var now = new Date().getFullYear(); 414 | 415 | if (sorted.length > 0) { 416 | $('#orgs').html(''); 417 | 418 | var name, view, template, html; 419 | 420 | $.each(sorted, function(index, org) { 421 | if (itemCount >= maxItems) { 422 | return; 423 | } 424 | name = (org.info.name || org.info.login); 425 | view = { 426 | name: name, 427 | now: now 428 | }; 429 | 430 | if (itemCount == sorted.length - 1 || itemCount == maxItems) { 431 | view.last = 'last'; 432 | } 433 | template = response; 434 | html = Mustache.to_html(template, view); 435 | 436 | $('#orgs').append($(html)); 437 | ++itemCount; 438 | }); 439 | } else { 440 | $('#organizations').remove(); 441 | } 442 | } 443 | }); 444 | }); 445 | 446 | }; 447 | 448 | if (trackerId) { 449 | var _gaq = _gaq || []; 450 | _gaq.push(['_setAccount', trackerId]); 451 | _gaq.push(['_trackPageview']); 452 | 453 | (function() { 454 | var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; 455 | ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 456 | var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); 457 | })(); 458 | } 459 | 460 | $(window).bind('error', error); 461 | --------------------------------------------------------------------------------