├── .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