• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

weshoke/Lust: Lua String Templates

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

weshoke/Lust

开源软件地址(OpenSource Url):

https://github.com/weshoke/Lust

开源编程语言(OpenSource Language):

Lua 100.0%

开源软件介绍(OpenSource Introduction):

Lust

Lua String Templates

Overview

Lust is a templating system for Lua loosely based on Terrence Parr's StringTemplate. Lust has been designed to enable the simple expression of complex string synthesis problems. It is particularly well suited for generating strings from hierarchical data structures. Lust itself encompases a language for writing templates and an interpreter for applying the templates to a datastructure.

Lust features

  • scoped templates
  • dynamic template dispatch based on runtime information
  • iteration through map and numeric loop mechanisms
  • conditional template application
  • whitespace indentation preservation
  • insertion of separator tokens between strings generated via iteration

Authors

Lust is developed by Wesley Smith and Graham Wakefield with the support of Cycling '74.

License

Lust is licensed under the standard MIT license just like the Lua Language.

Dependencies

Lust depends on the Lua Parsing Expression Grammar (LPEG) module.

High-Level Overview

In Lust, you author a set of templates, giving each one a name. Templates can be grouped together by putting them in a common namespace. To use the templates for string synthesis, a datastructure is passed to the Lust object, which initiates the synthesis process by applying the root template rule to the datastructure. As the root rule invokes subtemplates, Lust walks the datastructure passed in by following the commands described by operators embedded in the templates. Lust walks a datastructure either by iterating over arrays of values or by looking at named fields.

Lust Basics

The most fundamental structures in Lust are templates and environments. Templates are named strings, and environments represent the set of values a template has access to. The environment of a template is just like the concept of scope in a programming language. It provides a set of named values that can be referenced and operated on.

Stringification

Stringification takes a value and renders it directly into a string. In Lust, the stringification operator is indicated by the $ symbol.

-- $. applies the current environment:
Lust([[$.]]):gen("hello") -- res: "hello"
-- $1 selects item from environment-as-array:
Lust([[$1 $2]]):gen{ "hello", "world" } -- res: "hello world"
-- $foo selects item from environment-as-dictionary:
Lust[[$foo $bar]]:gen{ foo="hello", bar="world" } -- res: "hello world"
-- $< > can be used to avoid ambiguity:
Lust[[$<1>2 $<foo>bar]]:gen{ "hello", foo="world" } -- res: "hello2 worldbar"
-- selections can be constructed as paths into the environment:
Lust[[$a.b.c $1.1.1]]:gen{ a={ b={ c="hello" } }, { { "world" } } } -- res: "hello world"
Lust[[$a.1 $1.b]]:gen{ a={ "hello" }, { b="world" } } -- res: "hello world"
-- the # symbol prints the length of an environment selection:
Lust[[$#.]]:gen{ 1, 2, 3 } -- res: "3"
Lust[[$#foo.bar]]:gen{ foo={ bar={ 1, 2, 3 } } } -- res: "3"
-- selections can be resolved dynamically using (x):
Lust[[$(x)]]:gen{ x="foo", foo="hello" } -- res: "hello"
Lust[[$(x.y).1]]:gen{ x={ y="foo" }, foo={"hello"} } -- res: "hello"

Template Application

Template application applies a template to a particular environment. The template invocation operator is indicated by the @ symbol.

-- the @name invokes a statically named sub-template:
local temp = Lust[[@child]]
-- define a subtemplate:
temp.child = "$1 to child"
temp:gen{"hello"} -- res: "hello to child"
-- subtemplates can also be specified in the constructor-table:
Lust{
	[[@child]],
	child = "$1 to child",
}:gen{"hello"}	-- res: "hello to child"
-- subtemplate invocations can use < > to avoid ambiguity:
Lust{
	[[@<child>hood]],
	child = "$1 to child",
}:gen{"hello"} -- res: "hello to childhood"
-- subtemplates with subtemplates:
Lust{
	[[@child, @child.grandchild]],
	child = {
		"$1 to child",
		grandchild = "$1 to grandchild",
	},
}:gen{"hello"} -- res: "hello to child, hello to grandchild"
-- subtemplates with subtemplates (alternative naming):
Lust{
	[[@child, @child.grandchild]],
	child = "$1 to child",
	["child.grandchild"] = "$1 to grandchild",
}:gen{"hello"} -- res: "hello to child, hello to grandchild"
-- subtemplate names can also be resolved dynamically, according to model values, using (x):
Lust{
	[[@(x), @(y)]],
	child1 = "hello world",
	child2 = "hi"
}:gen{ x="child1", y="child2" } -- res: "hello world, hi"
-- the environment passed to a subtemplate can be specifed as a child of the current environment:
Lust{
	[[@1:child @two:child]],
	child = [[$. child]],
}:gen{ "one", two="two" } -- res: "one child two child"
-- the symbol . can be used to explicitly refer to the current environment:
Lust{
	[[@child == @.:child]],
	child = [[$1 child]],
}:gen{ "hello" } -- res: "hello child == hello child"
-- subtemplate paths can mix static and dynamic terms:
Lust{[[@child.(x), @(y).grandchild, @(a.b)]], 
	child ="$1 to child",
	["child.grandchild"] = "$1 to grandchild",
}:gen{ 
	x="grandchild", 
	y="child", 
	"hello", 
	a = { b="child" } 
} -- res: "hello to grandchild, hello to grandchild, hello to child"
-- child environments can be specified using multi-part paths:
Lust{
	[[@a.1.foo:child]],
	child = [[$. child]],
}:gen{ a={ { foo="hello" } } } -- res: "hello child"
-- subtemplates can be specified inline using @{{ }}:
Lust([[@foo.bar:{{$1 $2}}]]):gen{ foo={ bar={ "hello", "world" } } } -- res: "hello world"
-- environments can also be specified dynamically
-- the @{ } construction is similar to Lua table construction
Lust([[@{ ., greeting="hello" }:{{$greeting $1.place}}]]):gen{ place="world" } -- res: "hello world"
Lust([[@{ "hello", a.b.place }:{{$1 $2}}]]):gen{ a = { b = { place="world" } } } -- res: "hello world"
Lust([[@{ 1, place=a.b }:{{$1 $place.1}}]]):gen{ "hello", a = { b = { "world" } } } -- res: "hello world"
-- dynamic environments can contain arrays:
Lust([[@{ args=["hello", a.b] }:{{$args.1 $args.2.1}}]]):gen{ a = { b = { "world" } } } -- res: "hello world"
-- dynamic environments can contain subtemplate applications:
Lust{
	[[@{ .:child, a=x:child.grandchild }:{{$1, $a}}]],
	child = "$1 to child",
	["child.grandchild"] = "$1 to grandchild",
}:gen{ "hi", x = { "hello" } } -- res: "hi to child, hello to grandchild"
-- dynamic environments can be nested:
Lust{
	[[@{ { "hello" }, foo={ bar="world" } }:sub]],
	sub = [[$1.1 $foo.bar]],
}:gen{} -- res: "hello world"

Conditions

The @if condition takes a boolean expression and applies a template or value if it evaluates to true. If there is a corresponding else template/value, then it will be applied if the expression evaluates to false

-- conditional templates have a conditional test followed by a template application
-- @if(x) tests for the existence of x in the model
local temp = Lust{
	[[@if(x)<greet>]],
	greet = "hello",
}
temp:gen{ x=1 } 	-- res: "hello"
temp:gen{ } 		-- res: ""
-- @if(?(x)) evaluates x in the model, and then checks if the result is a valid template name
-- this example also demonstrates using dynamically evalutated template application:
local temp = Lust{
	[[@if(?(op))<(op)>]],
	child = "I am a child",
}
temp:gen{ op="child" } -- res: "I am a child"
-- using else and inline templates:
local temp = Lust[[@if(x)<{{hello}}>else<{{bye bye}}>]]
temp:gen{ x=1 }	-- res: "hello"
temp:gen{  }	-- res: "bye bye"
-- @if(#x > n) tests that the number of items in the model term 'x' is greater than n:
local temp = Lust[[@if(#. > "0")<{{at least one}}>]]
temp:gen{ "a" }	-- res: "at least one")
temp:gen{  } 	-- res: ""
-- compound conditions:
local temp = Lust[[@if(#x > "0" and #x < "5")<{{success}}>]]
temp:gen{ x={ "a", "b", "c", "d" } }		-- res: "success"
temp:gen{ x={ "a", "b", "c", "d", "e" } }	-- res: ""
temp:gen{ x={  } }							-- res: ""
temp:gen{ }									-- res: ""
-- compound conditions:
local temp = Lust[[@if(x or not not not y)<{{success}}>else<{{fail}}>]]
temp:gen{ x=1 }			-- res: "success"
temp:gen{ x=1, y=1 }	-- res: "success"
temp:gen{ y=1 }			-- res: "fail"
temp:gen{ }				-- res: "success"
-- compound conditions:
local temp = Lust[[@if(n*"2"+"1" > #x)<{{success}}>else<{{fail}}>]]
temp:gen{ n=3, x = { "a", "b", "c" } }	-- res: "success"
temp:gen{ n=1, x = { "a", "b", "c" } }	-- res: "fail"

Iteration

Lust has two main methods for creating iteration statements: a map function and numeric iteration. For the @map function, there are a variety of ways that it can be called depdening on the situation

-- @map can iterate over arrays in the environment:
local temp = Lust[[@map{ n=numbers }:{{$n.name }}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	}
}
-- result: "one two three "
-- assigning mapped values a name in the environment
local temp = Lust[[@map{ n=numbers }:{{$n }}]]
temp:gen{
	numbers = { "one", "two", "three" }
}
-- result: "one two three "
-- the _separator field can be used to insert elements between items:
local temp = Lust[[@map{ n=numbers, _separator=", " }:{{$n.name}}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	}
}
-- result: "one, two, three"
-- _ can be used as a shorthand for _separator:
local temp = Lust[[@map{ n=numbers, _=", " }:{{$n.name}}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	}
}
-- result: "one, two, three"
-- a map can iterate over multiple arrays in parallel
local temp = Lust[[@map{ a=letters, n=numbers, _=", " }:{{$a $n.name}}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	},
	letters = {
		"a", "b", "c",
	}
}
-- res: "a one, b two, c three"
-- if parallel mapped items have different lengths, the longest is used:
local temp = Lust[[@map{ a=letters, n=numbers, _=", " }:{{$a $n.name}}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	},
	letters = {
		"a", "b", "c", "d",
	}
}
-- res: "a one, b two, c three, d "
-- if parallel mapped items are not arrays, they are repeated each time:
local temp = Lust[[@map{ a=letters, n=numbers, prefix="hello", count=#letters, _=", " }:{{$prefix $a $n.name of $count}}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	},
	letters = {
		"a", "b", "c", "d",
	}
}
-- res: "hello a one of 4, hello b two of 4, hello c three of 4, hello d  of 4"
-- the 'i1' and 'i0' fields are added automatically for one- and zero-based array indices:
local temp = Lust[[@map{ n=numbers }:{{$i0-$i1 $n.name }}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	}
}
-- res: "0-1 one 1-2 two 2-3 three "
-- if the map only contains an un-named array, each item of the array becomes the environment applied in each iteration:
local temp = Lust[["@map{ ., _separator='", "' }:{{$name}}"]]
temp:gen{
	{ name="one" },
	{ name="two" },
	{ name="three" },
}
-- res: '"one", "two", "three"'

local temp = Lust[[@map{ numbers, count=#numbers, _separator=", " }:{{$name of $count}}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	}
}
-- res: "one of 3, two of 3, three of 3"
-- @rest is like @map, but starts from the 2nd item:
local temp = Lust[[@rest{ a=letters, n=numbers, _separator=", " }:{{$a $n.name}}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	},
	letters = {
		"a", "b", "c",
	}
}
-- res: "b two, c three"
-- @iter can be used for an explicit number of repetitions:
local temp = Lust[[@iter{ "3" }:{{repeat $i1 }}]]
temp:gen{} -- res: "repeat 1 repeat 2 repeat 3 "
-- again, _separator works:
local temp = Lust[[@iter{ "3", _separator=", " }:{{repeat $i1}}]]
temp:gen{} -- res: "repeat 1, repeat 2, repeat 3"
-- @iter can take an array item; it will use the length of that item:
local temp = Lust[[@iter{ numbers, _separator=", " }:{{repeat $i1}}]]
temp:gen{
	numbers = {
		{ name="one" },
		{ name="two" },
		{ name="three" },
	}
}
-- res: "repeat 1, repeat 2, repeat 3"

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap