├── .gitignore ├── lambda ├── .python-version ├── functions │ ├── crawl │ │ ├── .gitignore │ │ ├── requirements.txt │ │ └── main.py │ └── worker │ │ ├── .gitignore │ │ ├── requirements.txt │ │ └── main.py ├── project.json └── install_pip_packages.sh ├── Gemfile ├── upload_public.sh └── public ├── js ├── index.js └── handlebars.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | /Gemfile.lock 2 | -------------------------------------------------------------------------------- /lambda/.python-version: -------------------------------------------------------------------------------- 1 | 2.7.11 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'nokogiri' 4 | 5 | -------------------------------------------------------------------------------- /lambda/functions/crawl/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !main.py 3 | !requirements.txt 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /lambda/functions/worker/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !main.py 3 | !requirements.txt 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /lambda/functions/crawl/requirements.txt: -------------------------------------------------------------------------------- 1 | lxml 2 | cssselect 3 | requests 4 | feedgen 5 | boto3 6 | -------------------------------------------------------------------------------- /lambda/functions/worker/requirements.txt: -------------------------------------------------------------------------------- 1 | lxml 2 | cssselect 3 | requests 4 | feedgen 5 | boto3 6 | -------------------------------------------------------------------------------- /upload_public.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | aws s3 cp public/ s3://github-trends.ryotarai.info --recursive --exclude "rss/.*" 4 | -------------------------------------------------------------------------------- /lambda/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github_trends_rss", 3 | "description": "GitHub Trends RSS", 4 | "memory": 128, 5 | "timeout": 5, 6 | "role": "arn:aws:iam::736804012041:role/github_trends_rss_lambda_function", 7 | "environment": {} 8 | } -------------------------------------------------------------------------------- /lambda/install_pip_packages.sh: -------------------------------------------------------------------------------- 1 | docker run -it 137112412989.dkr.ecr.us-west-2.amazonaws.com/amazonlinux:latest -v $(pwd):/work /bin/bash -c ' 2 | yum install -y python27-pip python27-devel gcc libxml2-devel libxslt-devel && 3 | pip install --upgrade pip && 4 | cd /work/functions/crawl && 5 | /usr/local/bin/pip install -r requirements.txt -t . && 6 | cd /work/functions/worker && 7 | /usr/local/bin/pip install -r requirements.txt -t . 8 | ' 9 | -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $.getJSON("languages.json", function(languages) { 3 | var source = $("#lang-template").html(); 4 | var template = Handlebars.compile(source); 5 | var row; 6 | for (var i = 0; i < languages.length; i++) { 7 | var language = languages[i]; 8 | var context = { 9 | langKey: language["key"], 10 | langName: language["name"], 11 | escapedLangKey: language["key"], 12 | }; 13 | var html = template(context); 14 | 15 | if (i % 4 == 0) { 16 | row = $('
'); 17 | } 18 | row.append(html); 19 | if (i % 4 == 3) { 20 | $('div#content').append(row); 21 | row = null; 22 | } 23 | } 24 | 25 | if (row !== null) { 26 | $('div#content').append(row); 27 | } 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /lambda/functions/crawl/main.py: -------------------------------------------------------------------------------- 1 | from lxml import html 2 | import cssselect 3 | import requests 4 | from feedgen.feed import FeedGenerator 5 | import boto3 6 | import json 7 | 8 | sqs = boto3.resource('sqs') 9 | s3 = boto3.resource('s3') 10 | bucket = 'github-trends.ryotarai.info' 11 | queue_name = 'github_trends_worker' 12 | 13 | def handle(event, context): 14 | page = requests.get('https://github.com/trending') 15 | tree = html.fromstring(page.content) 16 | 17 | languages = [] 18 | languages.append({"url": "https://github.com/trending", "name": "All languages", "key": "all"}) 19 | languages.append({"url": "https://github.com/trending/unknown", "name": "Unknown", "key": "unknown"}) 20 | aa = tree.cssselect("div.select-menu-list")[1].cssselect("a") 21 | for a in aa: 22 | url = a.get("href") 23 | key = url.split("/")[-1] 24 | languages.append({"url": url, "name": a.cssselect("span")[0].text, "key": key}) 25 | 26 | s3.Object(bucket, "languages.json").put(Body=json.dumps(languages), ContentType="application/json") 27 | queue = sqs.get_queue_by_name(QueueName=queue_name) 28 | for since in ["daily", "weekly", "monthly"]: 29 | for language in languages: 30 | body = json.dumps({"language": language, "since": since}) 31 | print(body) 32 | queue.send_message(MessageBody=body) 33 | 34 | if __name__ == '__main__': 35 | handle(None, None) 36 | 37 | -------------------------------------------------------------------------------- /lambda/functions/worker/main.py: -------------------------------------------------------------------------------- 1 | from lxml import html 2 | import cssselect 3 | import requests 4 | from feedgen.feed import FeedGenerator 5 | import boto3 6 | import json 7 | from datetime import datetime 8 | 9 | sqs = boto3.resource('sqs') 10 | s3 = boto3.resource('s3') 11 | bucket = 'github-trends.ryotarai.info' 12 | queue_name = 'github_trends_worker' 13 | 14 | def generate_rss(language, since): 15 | url = "{0}?since={1}".format(language["url"], since) 16 | file_name = "github_trends_{0}_{1}.rss".format(language["key"], since) 17 | title = "GitHub Trends - {0} - {1}".format(language["name"], since.capitalize()) 18 | 19 | print(url) 20 | page = requests.get(url) 21 | tree = html.fromstring(page.content) 22 | lis = tree.cssselect("ol.repo-list li") 23 | 24 | fg = FeedGenerator() 25 | fg.title(title) 26 | fg.link(href="http://github-trends.ryotarai.info/rss/{0}".format(file_name)) 27 | fg.description(title) 28 | index = 1 29 | for li in lis: 30 | a = li.cssselect("h3 a")[0] 31 | description = "" 32 | ps = li.cssselect("p") 33 | if len(ps) > 0: 34 | description = ps[0].text_content().strip() 35 | 36 | fe = fg.add_entry() 37 | fe.link(href="https://github.com{0}".format(a.get("href"))) 38 | fe.title("{0} (#{1} - {2} - {3})".format( 39 | a.text_content().strip().replace(" / ", "/"), 40 | index, 41 | language["name"], 42 | since.capitalize(), 43 | )) 44 | fe.description(description) 45 | index += 1 46 | rssfeed = fg.rss_str(pretty=True) 47 | s3.Object(bucket, 'rss/{0}'.format(file_name)).put(Body=rssfeed, ContentType="application/xml") 48 | 49 | def handle(event, context): 50 | started_at = datetime.now() 51 | queue = sqs.get_queue_by_name(QueueName=queue_name) 52 | while True: 53 | messages = queue.receive_messages(VisibilityTimeout=300, MaxNumberOfMessages=10) 54 | if len(messages) == 0: 55 | return 56 | for message in messages: 57 | if (datetime.now() - started_at).total_seconds() > 60 * 4 + 50: 58 | return 59 | print(message.body) 60 | data = json.loads(message.body) 61 | generate_rss(data["language"], data["since"]) 62 | message.delete() 63 | 64 | if __name__ == '__main__': 65 | handle(None, None) 66 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | GitHub Trends RSS 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |

GitHub Trends RSS

23 |

RSS feeds of GitHub Trends. Try to subscribe!

24 |

Go to GitHub Trends

25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 |
33 | 34 | 37 |
38 | 39 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /public/js/handlebars.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (C) 2011 by Yehuda Katz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | */ 24 | 25 | // lib/handlebars/browser-prefix.js 26 | var Handlebars = {}; 27 | 28 | (function(Handlebars, undefined) { 29 | ; 30 | // lib/handlebars/base.js 31 | 32 | Handlebars.VERSION = "1.0.0"; 33 | Handlebars.COMPILER_REVISION = 4; 34 | 35 | Handlebars.REVISION_CHANGES = { 36 | 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it 37 | 2: '== 1.0.0-rc.3', 38 | 3: '== 1.0.0-rc.4', 39 | 4: '>= 1.0.0' 40 | }; 41 | 42 | Handlebars.helpers = {}; 43 | Handlebars.partials = {}; 44 | 45 | var toString = Object.prototype.toString, 46 | functionType = '[object Function]', 47 | objectType = '[object Object]'; 48 | 49 | Handlebars.registerHelper = function(name, fn, inverse) { 50 | if (toString.call(name) === objectType) { 51 | if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); } 52 | Handlebars.Utils.extend(this.helpers, name); 53 | } else { 54 | if (inverse) { fn.not = inverse; } 55 | this.helpers[name] = fn; 56 | } 57 | }; 58 | 59 | Handlebars.registerPartial = function(name, str) { 60 | if (toString.call(name) === objectType) { 61 | Handlebars.Utils.extend(this.partials, name); 62 | } else { 63 | this.partials[name] = str; 64 | } 65 | }; 66 | 67 | Handlebars.registerHelper('helperMissing', function(arg) { 68 | if(arguments.length === 2) { 69 | return undefined; 70 | } else { 71 | throw new Error("Missing helper: '" + arg + "'"); 72 | } 73 | }); 74 | 75 | Handlebars.registerHelper('blockHelperMissing', function(context, options) { 76 | var inverse = options.inverse || function() {}, fn = options.fn; 77 | 78 | var type = toString.call(context); 79 | 80 | if(type === functionType) { context = context.call(this); } 81 | 82 | if(context === true) { 83 | return fn(this); 84 | } else if(context === false || context == null) { 85 | return inverse(this); 86 | } else if(type === "[object Array]") { 87 | if(context.length > 0) { 88 | return Handlebars.helpers.each(context, options); 89 | } else { 90 | return inverse(this); 91 | } 92 | } else { 93 | return fn(context); 94 | } 95 | }); 96 | 97 | Handlebars.K = function() {}; 98 | 99 | Handlebars.createFrame = Object.create || function(object) { 100 | Handlebars.K.prototype = object; 101 | var obj = new Handlebars.K(); 102 | Handlebars.K.prototype = null; 103 | return obj; 104 | }; 105 | 106 | Handlebars.logger = { 107 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, 108 | 109 | methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, 110 | 111 | // can be overridden in the host environment 112 | log: function(level, obj) { 113 | if (Handlebars.logger.level <= level) { 114 | var method = Handlebars.logger.methodMap[level]; 115 | if (typeof console !== 'undefined' && console[method]) { 116 | console[method].call(console, obj); 117 | } 118 | } 119 | } 120 | }; 121 | 122 | Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; 123 | 124 | Handlebars.registerHelper('each', function(context, options) { 125 | var fn = options.fn, inverse = options.inverse; 126 | var i = 0, ret = "", data; 127 | 128 | var type = toString.call(context); 129 | if(type === functionType) { context = context.call(this); } 130 | 131 | if (options.data) { 132 | data = Handlebars.createFrame(options.data); 133 | } 134 | 135 | if(context && typeof context === 'object') { 136 | if(context instanceof Array){ 137 | for(var j = context.length; i 2) { 351 | expected.push("'" + this.terminals_[p] + "'"); 352 | } 353 | if (this.lexer.showPosition) { 354 | errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; 355 | } else { 356 | errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); 357 | } 358 | this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); 359 | } 360 | } 361 | if (action[0] instanceof Array && action.length > 1) { 362 | throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); 363 | } 364 | switch (action[0]) { 365 | case 1: 366 | stack.push(symbol); 367 | vstack.push(this.lexer.yytext); 368 | lstack.push(this.lexer.yylloc); 369 | stack.push(action[1]); 370 | symbol = null; 371 | if (!preErrorSymbol) { 372 | yyleng = this.lexer.yyleng; 373 | yytext = this.lexer.yytext; 374 | yylineno = this.lexer.yylineno; 375 | yyloc = this.lexer.yylloc; 376 | if (recovering > 0) 377 | recovering--; 378 | } else { 379 | symbol = preErrorSymbol; 380 | preErrorSymbol = null; 381 | } 382 | break; 383 | case 2: 384 | len = this.productions_[action[1]][1]; 385 | yyval.$ = vstack[vstack.length - len]; 386 | yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; 387 | if (ranges) { 388 | yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; 389 | } 390 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); 391 | if (typeof r !== "undefined") { 392 | return r; 393 | } 394 | if (len) { 395 | stack = stack.slice(0, -1 * len * 2); 396 | vstack = vstack.slice(0, -1 * len); 397 | lstack = lstack.slice(0, -1 * len); 398 | } 399 | stack.push(this.productions_[action[1]][0]); 400 | vstack.push(yyval.$); 401 | lstack.push(yyval._$); 402 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; 403 | stack.push(newState); 404 | break; 405 | case 3: 406 | return true; 407 | } 408 | } 409 | return true; 410 | } 411 | }; 412 | /* Jison generated lexer */ 413 | var lexer = (function(){ 414 | var lexer = ({EOF:1, 415 | parseError:function parseError(str, hash) { 416 | if (this.yy.parser) { 417 | this.yy.parser.parseError(str, hash); 418 | } else { 419 | throw new Error(str); 420 | } 421 | }, 422 | setInput:function (input) { 423 | this._input = input; 424 | this._more = this._less = this.done = false; 425 | this.yylineno = this.yyleng = 0; 426 | this.yytext = this.matched = this.match = ''; 427 | this.conditionStack = ['INITIAL']; 428 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; 429 | if (this.options.ranges) this.yylloc.range = [0,0]; 430 | this.offset = 0; 431 | return this; 432 | }, 433 | input:function () { 434 | var ch = this._input[0]; 435 | this.yytext += ch; 436 | this.yyleng++; 437 | this.offset++; 438 | this.match += ch; 439 | this.matched += ch; 440 | var lines = ch.match(/(?:\r\n?|\n).*/g); 441 | if (lines) { 442 | this.yylineno++; 443 | this.yylloc.last_line++; 444 | } else { 445 | this.yylloc.last_column++; 446 | } 447 | if (this.options.ranges) this.yylloc.range[1]++; 448 | 449 | this._input = this._input.slice(1); 450 | return ch; 451 | }, 452 | unput:function (ch) { 453 | var len = ch.length; 454 | var lines = ch.split(/(?:\r\n?|\n)/g); 455 | 456 | this._input = ch + this._input; 457 | this.yytext = this.yytext.substr(0, this.yytext.length-len-1); 458 | //this.yyleng -= len; 459 | this.offset -= len; 460 | var oldLines = this.match.split(/(?:\r\n?|\n)/g); 461 | this.match = this.match.substr(0, this.match.length-1); 462 | this.matched = this.matched.substr(0, this.matched.length-1); 463 | 464 | if (lines.length-1) this.yylineno -= lines.length-1; 465 | var r = this.yylloc.range; 466 | 467 | this.yylloc = {first_line: this.yylloc.first_line, 468 | last_line: this.yylineno+1, 469 | first_column: this.yylloc.first_column, 470 | last_column: lines ? 471 | (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: 472 | this.yylloc.first_column - len 473 | }; 474 | 475 | if (this.options.ranges) { 476 | this.yylloc.range = [r[0], r[0] + this.yyleng - len]; 477 | } 478 | return this; 479 | }, 480 | more:function () { 481 | this._more = true; 482 | return this; 483 | }, 484 | less:function (n) { 485 | this.unput(this.match.slice(n)); 486 | }, 487 | pastInput:function () { 488 | var past = this.matched.substr(0, this.matched.length - this.match.length); 489 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); 490 | }, 491 | upcomingInput:function () { 492 | var next = this.match; 493 | if (next.length < 20) { 494 | next += this._input.substr(0, 20-next.length); 495 | } 496 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); 497 | }, 498 | showPosition:function () { 499 | var pre = this.pastInput(); 500 | var c = new Array(pre.length + 1).join("-"); 501 | return pre + this.upcomingInput() + "\n" + c+"^"; 502 | }, 503 | next:function () { 504 | if (this.done) { 505 | return this.EOF; 506 | } 507 | if (!this._input) this.done = true; 508 | 509 | var token, 510 | match, 511 | tempMatch, 512 | index, 513 | col, 514 | lines; 515 | if (!this._more) { 516 | this.yytext = ''; 517 | this.match = ''; 518 | } 519 | var rules = this._currentRules(); 520 | for (var i=0;i < rules.length; i++) { 521 | tempMatch = this._input.match(this.rules[rules[i]]); 522 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { 523 | match = tempMatch; 524 | index = i; 525 | if (!this.options.flex) break; 526 | } 527 | } 528 | if (match) { 529 | lines = match[0].match(/(?:\r\n?|\n).*/g); 530 | if (lines) this.yylineno += lines.length; 531 | this.yylloc = {first_line: this.yylloc.last_line, 532 | last_line: this.yylineno+1, 533 | first_column: this.yylloc.last_column, 534 | last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; 535 | this.yytext += match[0]; 536 | this.match += match[0]; 537 | this.matches = match; 538 | this.yyleng = this.yytext.length; 539 | if (this.options.ranges) { 540 | this.yylloc.range = [this.offset, this.offset += this.yyleng]; 541 | } 542 | this._more = false; 543 | this._input = this._input.slice(match[0].length); 544 | this.matched += match[0]; 545 | token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); 546 | if (this.done && this._input) this.done = false; 547 | if (token) return token; 548 | else return; 549 | } 550 | if (this._input === "") { 551 | return this.EOF; 552 | } else { 553 | return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), 554 | {text: "", token: null, line: this.yylineno}); 555 | } 556 | }, 557 | lex:function lex() { 558 | var r = this.next(); 559 | if (typeof r !== 'undefined') { 560 | return r; 561 | } else { 562 | return this.lex(); 563 | } 564 | }, 565 | begin:function begin(condition) { 566 | this.conditionStack.push(condition); 567 | }, 568 | popState:function popState() { 569 | return this.conditionStack.pop(); 570 | }, 571 | _currentRules:function _currentRules() { 572 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; 573 | }, 574 | topState:function () { 575 | return this.conditionStack[this.conditionStack.length-2]; 576 | }, 577 | pushState:function begin(condition) { 578 | this.begin(condition); 579 | }}); 580 | lexer.options = {}; 581 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { 582 | 583 | var YYSTATE=YY_START 584 | switch($avoiding_name_collisions) { 585 | case 0: yy_.yytext = "\\"; return 14; 586 | break; 587 | case 1: 588 | if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); 589 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); 590 | if(yy_.yytext) return 14; 591 | 592 | break; 593 | case 2: return 14; 594 | break; 595 | case 3: 596 | if(yy_.yytext.slice(-1) !== "\\") this.popState(); 597 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); 598 | return 14; 599 | 600 | break; 601 | case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; 602 | break; 603 | case 5: return 25; 604 | break; 605 | case 6: return 16; 606 | break; 607 | case 7: return 20; 608 | break; 609 | case 8: return 19; 610 | break; 611 | case 9: return 19; 612 | break; 613 | case 10: return 23; 614 | break; 615 | case 11: return 22; 616 | break; 617 | case 12: this.popState(); this.begin('com'); 618 | break; 619 | case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; 620 | break; 621 | case 14: return 22; 622 | break; 623 | case 15: return 37; 624 | break; 625 | case 16: return 36; 626 | break; 627 | case 17: return 36; 628 | break; 629 | case 18: return 40; 630 | break; 631 | case 19: /*ignore whitespace*/ 632 | break; 633 | case 20: this.popState(); return 24; 634 | break; 635 | case 21: this.popState(); return 18; 636 | break; 637 | case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31; 638 | break; 639 | case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31; 640 | break; 641 | case 24: return 38; 642 | break; 643 | case 25: return 33; 644 | break; 645 | case 26: return 33; 646 | break; 647 | case 27: return 32; 648 | break; 649 | case 28: return 36; 650 | break; 651 | case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36; 652 | break; 653 | case 30: return 'INVALID'; 654 | break; 655 | case 31: return 5; 656 | break; 657 | } 658 | }; 659 | lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; 660 | lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}}; 661 | return lexer;})() 662 | parser.lexer = lexer; 663 | function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; 664 | return new Parser; 665 | })();; 666 | // lib/handlebars/compiler/base.js 667 | 668 | Handlebars.Parser = handlebars; 669 | 670 | Handlebars.parse = function(input) { 671 | 672 | // Just return if an already-compile AST was passed in. 673 | if(input.constructor === Handlebars.AST.ProgramNode) { return input; } 674 | 675 | Handlebars.Parser.yy = Handlebars.AST; 676 | return Handlebars.Parser.parse(input); 677 | }; 678 | ; 679 | // lib/handlebars/compiler/ast.js 680 | Handlebars.AST = {}; 681 | 682 | Handlebars.AST.ProgramNode = function(statements, inverse) { 683 | this.type = "program"; 684 | this.statements = statements; 685 | if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } 686 | }; 687 | 688 | Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { 689 | this.type = "mustache"; 690 | this.escaped = !unescaped; 691 | this.hash = hash; 692 | 693 | var id = this.id = rawParams[0]; 694 | var params = this.params = rawParams.slice(1); 695 | 696 | // a mustache is an eligible helper if: 697 | // * its id is simple (a single part, not `this` or `..`) 698 | var eligibleHelper = this.eligibleHelper = id.isSimple; 699 | 700 | // a mustache is definitely a helper if: 701 | // * it is an eligible helper, and 702 | // * it has at least one parameter or hash segment 703 | this.isHelper = eligibleHelper && (params.length || hash); 704 | 705 | // if a mustache is an eligible helper but not a definite 706 | // helper, it is ambiguous, and will be resolved in a later 707 | // pass or at runtime. 708 | }; 709 | 710 | Handlebars.AST.PartialNode = function(partialName, context) { 711 | this.type = "partial"; 712 | this.partialName = partialName; 713 | this.context = context; 714 | }; 715 | 716 | Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { 717 | var verifyMatch = function(open, close) { 718 | if(open.original !== close.original) { 719 | throw new Handlebars.Exception(open.original + " doesn't match " + close.original); 720 | } 721 | }; 722 | 723 | verifyMatch(mustache.id, close); 724 | this.type = "block"; 725 | this.mustache = mustache; 726 | this.program = program; 727 | this.inverse = inverse; 728 | 729 | if (this.inverse && !this.program) { 730 | this.isInverse = true; 731 | } 732 | }; 733 | 734 | Handlebars.AST.ContentNode = function(string) { 735 | this.type = "content"; 736 | this.string = string; 737 | }; 738 | 739 | Handlebars.AST.HashNode = function(pairs) { 740 | this.type = "hash"; 741 | this.pairs = pairs; 742 | }; 743 | 744 | Handlebars.AST.IdNode = function(parts) { 745 | this.type = "ID"; 746 | 747 | var original = "", 748 | dig = [], 749 | depth = 0; 750 | 751 | for(var i=0,l=parts.length; i 0) { throw new Handlebars.Exception("Invalid path: " + original); } 757 | else if (part === "..") { depth++; } 758 | else { this.isScoped = true; } 759 | } 760 | else { dig.push(part); } 761 | } 762 | 763 | this.original = original; 764 | this.parts = dig; 765 | this.string = dig.join('.'); 766 | this.depth = depth; 767 | 768 | // an ID is simple if it only has one part, and that part is not 769 | // `..` or `this`. 770 | this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; 771 | 772 | this.stringModeValue = this.string; 773 | }; 774 | 775 | Handlebars.AST.PartialNameNode = function(name) { 776 | this.type = "PARTIAL_NAME"; 777 | this.name = name.original; 778 | }; 779 | 780 | Handlebars.AST.DataNode = function(id) { 781 | this.type = "DATA"; 782 | this.id = id; 783 | }; 784 | 785 | Handlebars.AST.StringNode = function(string) { 786 | this.type = "STRING"; 787 | this.original = 788 | this.string = 789 | this.stringModeValue = string; 790 | }; 791 | 792 | Handlebars.AST.IntegerNode = function(integer) { 793 | this.type = "INTEGER"; 794 | this.original = 795 | this.integer = integer; 796 | this.stringModeValue = Number(integer); 797 | }; 798 | 799 | Handlebars.AST.BooleanNode = function(bool) { 800 | this.type = "BOOLEAN"; 801 | this.bool = bool; 802 | this.stringModeValue = bool === "true"; 803 | }; 804 | 805 | Handlebars.AST.CommentNode = function(comment) { 806 | this.type = "comment"; 807 | this.comment = comment; 808 | }; 809 | ; 810 | // lib/handlebars/utils.js 811 | 812 | var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; 813 | 814 | Handlebars.Exception = function(message) { 815 | var tmp = Error.prototype.constructor.apply(this, arguments); 816 | 817 | // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. 818 | for (var idx = 0; idx < errorProps.length; idx++) { 819 | this[errorProps[idx]] = tmp[errorProps[idx]]; 820 | } 821 | }; 822 | Handlebars.Exception.prototype = new Error(); 823 | 824 | // Build out our basic SafeString type 825 | Handlebars.SafeString = function(string) { 826 | this.string = string; 827 | }; 828 | Handlebars.SafeString.prototype.toString = function() { 829 | return this.string.toString(); 830 | }; 831 | 832 | var escape = { 833 | "&": "&", 834 | "<": "<", 835 | ">": ">", 836 | '"': """, 837 | "'": "'", 838 | "`": "`" 839 | }; 840 | 841 | var badChars = /[&<>"'`]/g; 842 | var possible = /[&<>"'`]/; 843 | 844 | var escapeChar = function(chr) { 845 | return escape[chr] || "&"; 846 | }; 847 | 848 | Handlebars.Utils = { 849 | extend: function(obj, value) { 850 | for(var key in value) { 851 | if(value.hasOwnProperty(key)) { 852 | obj[key] = value[key]; 853 | } 854 | } 855 | }, 856 | 857 | escapeExpression: function(string) { 858 | // don't escape SafeStrings, since they're already safe 859 | if (string instanceof Handlebars.SafeString) { 860 | return string.toString(); 861 | } else if (string == null || string === false) { 862 | return ""; 863 | } 864 | 865 | // Force a string conversion as this will be done by the append regardless and 866 | // the regex test will do this transparently behind the scenes, causing issues if 867 | // an object's to string has escaped characters in it. 868 | string = string.toString(); 869 | 870 | if(!possible.test(string)) { return string; } 871 | return string.replace(badChars, escapeChar); 872 | }, 873 | 874 | isEmpty: function(value) { 875 | if (!value && value !== 0) { 876 | return true; 877 | } else if(toString.call(value) === "[object Array]" && value.length === 0) { 878 | return true; 879 | } else { 880 | return false; 881 | } 882 | } 883 | }; 884 | ; 885 | // lib/handlebars/compiler/compiler.js 886 | 887 | /*jshint eqnull:true*/ 888 | var Compiler = Handlebars.Compiler = function() {}; 889 | var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {}; 890 | 891 | // the foundHelper register will disambiguate helper lookup from finding a 892 | // function in a context. This is necessary for mustache compatibility, which 893 | // requires that context functions in blocks are evaluated by blockHelperMissing, 894 | // and then proceed as if the resulting value was provided to blockHelperMissing. 895 | 896 | Compiler.prototype = { 897 | compiler: Compiler, 898 | 899 | disassemble: function() { 900 | var opcodes = this.opcodes, opcode, out = [], params, param; 901 | 902 | for (var i=0, l=opcodes.length; i 0) { 1414 | this.source[1] = this.source[1] + ", " + locals.join(", "); 1415 | } 1416 | 1417 | // Generate minimizer alias mappings 1418 | if (!this.isChild) { 1419 | for (var alias in this.context.aliases) { 1420 | if (this.context.aliases.hasOwnProperty(alias)) { 1421 | this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; 1422 | } 1423 | } 1424 | } 1425 | 1426 | if (this.source[1]) { 1427 | this.source[1] = "var " + this.source[1].substring(2) + ";"; 1428 | } 1429 | 1430 | // Merge children 1431 | if (!this.isChild) { 1432 | this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; 1433 | } 1434 | 1435 | if (!this.environment.isSimple) { 1436 | this.source.push("return buffer;"); 1437 | } 1438 | 1439 | var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; 1440 | 1441 | for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } 1975 | return this.topStackName(); 1976 | }, 1977 | topStackName: function() { 1978 | return "stack" + this.stackSlot; 1979 | }, 1980 | flushInline: function() { 1981 | var inlineStack = this.inlineStack; 1982 | if (inlineStack.length) { 1983 | this.inlineStack = []; 1984 | for (var i = 0, len = inlineStack.length; i < len; i++) { 1985 | var entry = inlineStack[i]; 1986 | if (entry instanceof Literal) { 1987 | this.compileStack.push(entry); 1988 | } else { 1989 | this.pushStack(entry); 1990 | } 1991 | } 1992 | } 1993 | }, 1994 | isInline: function() { 1995 | return this.inlineStack.length; 1996 | }, 1997 | 1998 | popStack: function(wrapped) { 1999 | var inline = this.isInline(), 2000 | item = (inline ? this.inlineStack : this.compileStack).pop(); 2001 | 2002 | if (!wrapped && (item instanceof Literal)) { 2003 | return item.value; 2004 | } else { 2005 | if (!inline) { 2006 | this.stackSlot--; 2007 | } 2008 | return item; 2009 | } 2010 | }, 2011 | 2012 | topStack: function(wrapped) { 2013 | var stack = (this.isInline() ? this.inlineStack : this.compileStack), 2014 | item = stack[stack.length - 1]; 2015 | 2016 | if (!wrapped && (item instanceof Literal)) { 2017 | return item.value; 2018 | } else { 2019 | return item; 2020 | } 2021 | }, 2022 | 2023 | quotedString: function(str) { 2024 | return '"' + str 2025 | .replace(/\\/g, '\\\\') 2026 | .replace(/"/g, '\\"') 2027 | .replace(/\n/g, '\\n') 2028 | .replace(/\r/g, '\\r') 2029 | .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 2030 | .replace(/\u2029/g, '\\u2029') + '"'; 2031 | }, 2032 | 2033 | setupHelper: function(paramSize, name, missingParams) { 2034 | var params = []; 2035 | this.setupParams(paramSize, params, missingParams); 2036 | var foundHelper = this.nameLookup('helpers', name, 'helper'); 2037 | 2038 | return { 2039 | params: params, 2040 | name: foundHelper, 2041 | callParams: ["depth0"].concat(params).join(", "), 2042 | helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") 2043 | }; 2044 | }, 2045 | 2046 | // the params and contexts arguments are passed in arrays 2047 | // to fill in 2048 | setupParams: function(paramSize, params, useRegister) { 2049 | var options = [], contexts = [], types = [], param, inverse, program; 2050 | 2051 | options.push("hash:" + this.popStack()); 2052 | 2053 | inverse = this.popStack(); 2054 | program = this.popStack(); 2055 | 2056 | // Avoid setting fn and inverse if neither are set. This allows 2057 | // helpers to do a check for `if (options.fn)` 2058 | if (program || inverse) { 2059 | if (!program) { 2060 | this.context.aliases.self = "this"; 2061 | program = "self.noop"; 2062 | } 2063 | 2064 | if (!inverse) { 2065 | this.context.aliases.self = "this"; 2066 | inverse = "self.noop"; 2067 | } 2068 | 2069 | options.push("inverse:" + inverse); 2070 | options.push("fn:" + program); 2071 | } 2072 | 2073 | for(var i=0; i