Merge pull request #697 from wildfireapp/master

The following adds a simple liquid parser we've been using internally
This commit is contained in:
Fabian Jakobs 2012-04-05 12:49:25 -07:00
commit a6112a9128
7 changed files with 510 additions and 1 deletions

View file

@ -218,7 +218,7 @@ function buildAce(aceProject, options) {
compat: true,
modes: [
"css", "html", "javascript", "php", "coldfusion", "python", "lua", "xml", "ruby", "java", "c_cpp",
"coffee", "perl", "csharp", "haxe", "svg", "clojure", "scss", "json", "groovy",
"coffee", "perl", "csharp", "haxe", "liquid", "svg", "clojure", "scss", "json", "groovy",
"ocaml", "scala", "textile", "scad", "markdown", "latex", "powershell", "sql",
"text", "pgsql", "sh", "xquery"
],

View file

@ -98,6 +98,7 @@ var modes = [
new Mode("json", "JSON", ["json"]),
new Mode("latex", "LaTeX", ["tex"]),
new Mode("lua", "Lua", ["lua"]),
new Mode("liquid", "Liquid", ["liquid"]),
new Mode("markdown", "Markdown", ["md", "markdown"]),
new Mode("ocaml", "OCaml", ["ml", "mli"]),
new Mode("perl", "Perl", ["pl", "pm"]),
@ -189,6 +190,10 @@ var docs = [
"lua", "Lua",
require("ace/requirejs/text!./docs/lua.lua")
),
new Doc(
"liquid", "Liquid",
require("ace/requirejs/text!./docs/liquid.liquid")
),
new Doc(
"java", "Java",
require("ace/requirejs/text!./docs/java.java")

View file

@ -0,0 +1,76 @@
The following examples can be found in full at http://liquidmarkup.org/
Liquid is an extraction from the e-commerce system Shopify.
Shopify powers many thousands of e-commerce stores which all call for unique designs.
For this we developed Liquid which allows our customers complete design freedom while
maintaining the integrity of our servers.
Liquid has been in production use since June 2006 and is now used by many other
hosted web applications.
It was developed for usage in Ruby on Rails web applications and integrates seamlessly
as a plugin but it also works excellently as a stand alone library.
Here's what it looks like:
<ul id="products">
{% for product in products %}
<li>
<h2>{{ product.title }}</h2>
Only {{ product.price | format_as_money }}
<p>{{ product.description | prettyprint | truncate: 200 }}</p>
</li>
{% endfor %}
</ul>
Some more features include:
<h2>Filters</h2>
<p> The word "tobi" in uppercase: {{ 'tobi' | upcase }} </p>
<p>The word "tobi" has {{ 'tobi' | size }} letters! </p>
<p>Change "Hello world" to "Hi world": {{ 'Hello world' | replace: 'Hello', 'Hi' }} </p>
<p>The date today is {{ 'now' | date: "%Y %b %d" }} </p>
<h2>If</h2>
<p>
{% if user.name == 'tobi' or user.name == 'marc' %}
hi marc or tobi
{% endif %}
</p>
<h2>Case</h2>
<p>
{% case template %}
{% when 'index' %}
Welcome
{% when 'product' %}
{{ product.vendor | link_to_vendor }} / {{ product.title }}
{% else %}
{{ page_title }}
{% endcase %}
</p>
<h2>For Loops</h2>
<p>
{% for item in array %}
{{ item }}
{% endfor %}
</p>
<h2>Tables</h2>
<p>
{% tablerow item in items cols: 3 %}
{% if tablerowloop.col_first %}
First column: {{ item.variable }}
{% else %}
Different column: {{ item.variable }}
{% endif %}
{% endtablerow %}
</p>

View file

@ -395,6 +395,7 @@ function setupSettingPanel(settingDiv, settingOpener, api, options) {
svg: "SVG",
textile: "Textile",
groovy: "Groovy",
liquid: "Liquid",
Scala: "Scala"
},
theme: {

116
lib/ace/mode/liquid.js Normal file
View file

@ -0,0 +1,116 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var Tokenizer = require("../tokenizer").Tokenizer;
var LiquidHighlightRules = require("./liquid_highlight_rules").LiquidHighlightRules;
var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent;
var Range = require("../range").Range;
var Mode = function() {
this.$tokenizer = new Tokenizer(new LiquidHighlightRules().getRules());
this.$outdent = new MatchingBraceOutdent();
};
oop.inherits(Mode, TextMode);
(function() {
this.toggleCommentLines = function(state, doc, startRow, endRow) {
var outdent = true;
var outentedRows = [];
var re = /^(\s*)#/;
for (var i=startRow; i<= endRow; i++) {
if (!re.test(doc.getLine(i))) {
outdent = false;
break;
}
}
if (outdent) {
var deleteRange = new Range(0, 0, 0, 0);
for (var i=startRow; i<= endRow; i++)
{
var line = doc.getLine(i);
var m = line.match(re);
deleteRange.start.row = i;
deleteRange.end.row = i;
deleteRange.end.column = m[0].length;
doc.replace(deleteRange, m[1]);
}
}
else {
doc.indentRows(startRow, endRow, "#");
}
};
this.getNextLineIndent = function(state, line, tab) {
var indent = this.$getIndent(line);
var tokenizedLine = this.$tokenizer.getLineTokens(line, state);
var tokens = tokenizedLine.tokens;
var endState = tokenizedLine.state;
if (tokens.length && tokens[tokens.length-1].type == "comment") {
return indent;
}
if (state == "start") {
var match = line.match(/^.*[\{\(\[]\s*$/);
if (match) {
indent += tab;
}
}
return indent;
};
this.checkOutdent = function(state, line, input) {
return this.$outdent.checkOutdent(line, input);
};
this.autoOutdent = function(state, doc, row) {
this.$outdent.autoOutdent(doc, row);
};
}).call(Mode.prototype);
exports.Mode = Mode;
});

View file

@ -0,0 +1,220 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org>
* Mihai Sucan <mihai DOT sucan AT gmail DOT com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
var oop = require("../lib/oop");
var CssHighlightRules = require("./css_highlight_rules").CssHighlightRules;
var JavaScriptHighlightRules = require("./javascript_highlight_rules").JavaScriptHighlightRules;
var lang = require("../lib/lang");
var xmlUtil = require("./xml_util");
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
var LiquidHighlightRules = function() {
// see: https://developer.mozilla.org/en/Liquid/Reference/Global_Objects
var functions = lang.arrayToMap(
// Standard Filters
("date|capitalize|downcase|upcase|first|last|join|sort|map|size|escape|" +
"escape_once|strip_html|strip_newlines|newline_to_br|replace|replace_first|" +
"truncate|truncatewords|prepend|append|minus|plus|times|divided_by|split"
).split("|")
);
var keywords = lang.arrayToMap(
// Standard Tags
("capture|endcapture|case|endcase|when|comment|endcomment|" +
"cycle|for|endfor|in|reversed|if|endif|else|elsif|include|endinclude|unless|endunless|" +
// Commonly used tags
"style|text|image|widget|plugin|marker|endmarker|tablerow|endtablerow").split("|")
);
var builtinVariables = lang.arrayToMap(
['forloop']
// ("forloop\\.(length|index|index0|rindex|rindex0|first|last)|limit|offset|range" +
// "tablerowloop\\.(length|index|index0|rindex|rindex0|first|last|col|col0|"+
// "col_first|col_last)").split("|")
);
var definitions = lang.arrayToMap(("assign").split("|"));
// regexp must not have capturing parentheses. Use (?:) instead.
// regexps are ordered -> the first match is used
this.$rules = {
start : [{
token : "variable",
regex : "{%",
next : "liquid_start"
}, {
token : "variable",
regex : "{{",
next : "liquid_start"
}, {
token : "meta.tag",
merge : true,
regex : "<\\!\\[CDATA\\[",
next : "cdata"
}, {
token : "xml_pe",
regex : "<\\?.*?\\?>"
}, {
token : "comment",
merge : true,
regex : "<\\!--",
next : "comment"
}, {
token : "meta.tag",
regex : "<(?=\s*script\\b)",
next : "script"
}, {
token : "meta.tag",
regex : "<(?=\s*style\\b)",
next : "style"
}, {
token : "meta.tag", // opening tag
regex : "<\\/?",
next : "tag"
}, {
token : "text",
regex : "\\s+"
}, {
token : "text",
regex : "[^<]+"
} ],
cdata : [ {
token : "text",
regex : "\\]\\]>",
next : "start"
}, {
token : "text",
merge : true,
regex : "\\s+"
}, {
token : "text",
merge : true,
regex : ".+"
} ],
comment : [ {
token : "comment",
regex : ".*?-->",
next : "start"
}, {
token : "comment",
merge : true,
regex : ".+"
} ] ,
liquid_start : [{
token: "variable",
regex: "}}",
next: "start"
}, {
token: "variable",
regex: "%}",
next: "start"
}, {
token : "string", // single line
regex : '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
}, {
token : "string", // single line
regex : "['](?:(?:\\\\.)|(?:[^'\\\\]))*?[']"
}, {
token : "constant.numeric", // hex
regex : "0[xX][0-9a-fA-F]+\\b"
}, {
token : "constant.numeric", // float
regex : "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
}, {
token : "constant.language.boolean",
regex : "(?:true|false)\\b"
}, {
token : function(value) {
if (functions.hasOwnProperty(value))
return "support.function";
else if (keywords.hasOwnProperty(value))
return "keyword";
else if (builtinVariables.hasOwnProperty(value))
return "variable.language";
else if (definitions.hasOwnProperty(value))
return "keyword.definition";
else
return "identifier";
},
regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
}, {
token : "keyword.operator",
regex : "\/|\\*|\\-|\\+|=|!=|\\?\\:"
}, {
token : "paren.lparen",
regex : "[[({]"
}, {
token : "paren.rparen",
regex : "[\\])}]"
}, {
token : "text",
regex : "\\s+"
}, ]
};
xmlUtil.tag(this.$rules, "tag", "start");
xmlUtil.tag(this.$rules, "style", "css-start");
xmlUtil.tag(this.$rules, "script", "js-start");
this.embedRules(JavaScriptHighlightRules, "js-", [{
token: "comment",
regex: "\\/\\/.*(?=<\\/script>)",
next: "tag"
}, {
token: "meta.tag",
regex: "<\\/(?=script)",
next: "tag"
}]);
this.embedRules(CssHighlightRules, "css-", [{
token: "meta.tag",
regex: "<\\/(?=style)",
next: "tag"
}]);
};
oop.inherits(LiquidHighlightRules, TextHighlightRules);
exports.LiquidHighlightRules = LiquidHighlightRules;
});

View file

@ -0,0 +1,91 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Ajax.org Code Editor (ACE).
*
* The Initial Developer of the Original Code is
* Ajax.org B.V.
* Portions created by the Initial Developer are Copyright (C) 2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fabian Jakobs <fabian AT ajax DOT org>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
if (typeof process !== "undefined") {
require("amd-loader");
}
define(function(require, exports, module) {
var LiquidMode = require("./liquid").Mode;
var assert = require("../test/assertions");
module.exports = {
name: "Liquid Tokenizer",
setUp : function() {
this.tokenizer = new LiquidMode().getTokenizer();
},
"test: tokenize tags" : function() {
var line = "for one in many";
var tokens = this.tokenizer.getLineTokens(line, "start").tokens;
assert.equal(5, tokens.length);
assert.equal("keyword", tokens[0].type);
assert.equal("text", tokens[1].type);
assert.equal("text", tokens[2].type);
assert.equal("text", tokens[3].type);
assert.equal("keyword.operator", tokens[3].type);
assert.equal("text", tokens[4].type);
assert.equal("text", tokens[5].type);
},
"test: tokenize parens" : function() {
var line = "[{( )}]";
var tokens = this.tokenizer.getLineTokens(line, "start").tokens;
assert.equal(7, tokens.length);
assert.equal("paren.lparen", tokens[0].type);
assert.equal("paren.lparen", tokens[1].type);
assert.equal("paren.lparen", tokens[2].type);
assert.equal("text", tokens[3].type);
assert.equal("paren.rparen", tokens[4].type);
assert.equal("paren.rparen", tokens[5].type);
assert.equal("paren.rparen", tokens[6].type);
}
};
});
if (typeof module !== "undefined" && module === require.main) {
require("asyncjs").test.testcase(module.exports).exec()
}