mirror of
https://onedev.site.tesses.net/crosslang/crosslangextras
synced 2026-02-08 17:15:45 +00:00
First commit
This commit is contained in:
11
Tesses.CrossLang.Markup/cross.json
Normal file
11
Tesses.CrossLang.Markup/cross.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Tesses.CrossLang.Markup",
|
||||
"version": "1.0.0.0-prod",
|
||||
"info": {
|
||||
"maintainer": "Mike Nolan",
|
||||
"type": "compile_tool",
|
||||
"repo": "https://gitea.site.tesses.net/tesses50/crosslang/crosslang-libs",
|
||||
"homepage": "https://crosslang.tesseslanguage.com/",
|
||||
"license": "LGPLv3"
|
||||
}
|
||||
}
|
||||
818
Tesses.CrossLang.Markup/src/markup.tcross
Normal file
818
Tesses.CrossLang.Markup/src/markup.tcross
Normal file
@@ -0,0 +1,818 @@
|
||||
func crossmarkuplexer(data)
|
||||
{
|
||||
var tokens = [];
|
||||
var i = 0;
|
||||
var peeked=null;
|
||||
var inSpecial=false;
|
||||
|
||||
var builder = "";
|
||||
func read()
|
||||
{
|
||||
if(peeked)
|
||||
{
|
||||
var myPeeked=peeked;
|
||||
peeked = null;
|
||||
return myPeeked;
|
||||
}
|
||||
if(i < data.Length) {
|
||||
var rc = data[i++];
|
||||
return rc;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
func peek()
|
||||
{
|
||||
if(peeked) return peeked;
|
||||
peeked = read();
|
||||
return peeked;
|
||||
}
|
||||
|
||||
|
||||
|
||||
func flush()
|
||||
{
|
||||
if(builder.Count > 0)
|
||||
{
|
||||
tokens.Add({
|
||||
Type = inSpecial ? "Identifier" : "Text",
|
||||
Text = builder
|
||||
});
|
||||
builder = "";
|
||||
}
|
||||
}
|
||||
|
||||
func ReadChar()
|
||||
{
|
||||
var r2 = read();
|
||||
|
||||
if(!r2) return null;
|
||||
if(r2 == '\\')
|
||||
{
|
||||
r2 = read();
|
||||
if(!r2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $"\\{r2}";
|
||||
}
|
||||
|
||||
return r2.ToString();
|
||||
}
|
||||
|
||||
func ReadString()
|
||||
{
|
||||
var str = "";
|
||||
while(var myChar = ReadChar())
|
||||
{
|
||||
if(myChar == "\"") break;
|
||||
str += myChar;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
while(r = read())
|
||||
{
|
||||
var p = peek();
|
||||
if(!inSpecial && r == '<')
|
||||
{
|
||||
if(p == '?')
|
||||
{
|
||||
read();
|
||||
|
||||
|
||||
flush();
|
||||
|
||||
tokens.Add({
|
||||
Type = "EnterSpecial"
|
||||
});
|
||||
|
||||
|
||||
inSpecial=true;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
builder += "<";
|
||||
}
|
||||
}
|
||||
else if(inSpecial && (r == '\r' || r == '\n' || r == ' ' || r == '\t'))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
else if(inSpecial && r == '\"')
|
||||
{
|
||||
flush();
|
||||
var myStr = ReadString();
|
||||
tokens.Add({
|
||||
Type = "String",
|
||||
Text = myStr
|
||||
});
|
||||
}
|
||||
else if(inSpecial && r == '\'')
|
||||
{
|
||||
flush();
|
||||
var myChr = ReadChar();
|
||||
tokens.Add({
|
||||
Type = "Char",
|
||||
Text = myChr
|
||||
});
|
||||
read();
|
||||
}
|
||||
else if(inSpecial && r == '=')
|
||||
{
|
||||
flush();
|
||||
tokens.Add({
|
||||
Type = "Equals"
|
||||
});
|
||||
}
|
||||
else if(inSpecial && r == '(')
|
||||
{
|
||||
flush();
|
||||
tokens.Add({
|
||||
Type = "OpenParen"
|
||||
});
|
||||
}
|
||||
else if(inSpecial && r == ')')
|
||||
{
|
||||
flush();
|
||||
tokens.Add({
|
||||
Type = "CloseParen"
|
||||
});
|
||||
}
|
||||
else if(inSpecial && r == ',')
|
||||
{
|
||||
flush();
|
||||
tokens.Add({
|
||||
Type = "Comma"
|
||||
});
|
||||
}
|
||||
else if(inSpecial && r == '?')
|
||||
{
|
||||
if(p == '?')
|
||||
{
|
||||
read();
|
||||
builder += "?";
|
||||
}
|
||||
else if(p == '>')
|
||||
{
|
||||
read();
|
||||
|
||||
flush();
|
||||
tokens.Add({
|
||||
Type = "ExitSpecial"
|
||||
});
|
||||
inSpecial=false;
|
||||
}
|
||||
else
|
||||
{
|
||||
builder += read().ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder += r.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
flush();
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
func crossmarkupparser(tokens)
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
func NotEnd()
|
||||
{
|
||||
if(i + 3 > tokens.Count) return false;
|
||||
|
||||
if(tokens[i].Type != "EnterSpecial")
|
||||
return true;
|
||||
|
||||
if(tokens[i+1].Type != "Identifier")
|
||||
return true;
|
||||
if(tokens[i+1].Text != "end")
|
||||
return true;
|
||||
if(tokens[i+2].Type != "ExitSpecial")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
func EnsureEnd()
|
||||
{
|
||||
if(NotEnd())
|
||||
{
|
||||
throw "Must end with <?end?>";
|
||||
}
|
||||
i += 3;
|
||||
}
|
||||
|
||||
func TokenTypeIs(type)
|
||||
{
|
||||
return i < tokens.Length && tokens[i].Type == type;
|
||||
}
|
||||
|
||||
func EnsureTokenType(type)
|
||||
{
|
||||
if(!TokenTypeIs(type))
|
||||
{
|
||||
throw $"The token is {type} which is not {tokens[i].Type}";
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
func ConsumeIfTokenTypeIs(type)
|
||||
{
|
||||
if(TokenTypeIs(type)) i++;
|
||||
}
|
||||
|
||||
func parse_node()
|
||||
{
|
||||
if(i < tokens.Length)
|
||||
{
|
||||
var myToken = tokens[i++];
|
||||
if(myToken.Type == "EnterSpecial")
|
||||
{
|
||||
if(i < tokens.Length)
|
||||
{
|
||||
var myToken2 = tokens[i++];
|
||||
if(myToken2.Type == "Identifier")
|
||||
{
|
||||
if(myToken2.Text == "page")
|
||||
{
|
||||
var pageargs = [];
|
||||
var nodes =[];
|
||||
var route = "/";
|
||||
var router_function = "Router";
|
||||
//its a page
|
||||
|
||||
EnsureTokenType("OpenParen");
|
||||
while(!TokenTypeIs("CloseParen"))
|
||||
{
|
||||
|
||||
ConsumeIfTokenTypeIs("Comma");
|
||||
if(i < tokens.Length && tokens[i].Type == "Identifier")
|
||||
{
|
||||
pageargs.Add(tokens[i].Text);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
EnsureTokenType("CloseParen");
|
||||
|
||||
while(!TokenTypeIs("ExitSpecial"))
|
||||
{
|
||||
if(TokenTypeIs("Identifier"))
|
||||
{
|
||||
var key = tokens[i++].Text;
|
||||
EnsureTokenType("Equals");
|
||||
if(TokenTypeIs("String"))
|
||||
{
|
||||
var value = tokens[i++].Text;
|
||||
if(key == "route")
|
||||
route = value;
|
||||
else if(key == "router_function")
|
||||
router_function = value;
|
||||
else
|
||||
throw $"Unknown property {key}";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Not a string";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Not an identifier";
|
||||
}
|
||||
}
|
||||
|
||||
EnsureTokenType("ExitSpecial");
|
||||
while(NotEnd())
|
||||
{
|
||||
nodes.Add(parse_node());
|
||||
}
|
||||
EnsureEnd();
|
||||
|
||||
|
||||
return {
|
||||
|
||||
Type = "PageNode",
|
||||
Arguments = pageargs,
|
||||
Route = route,
|
||||
RouterFunction = router_function,
|
||||
Nodes = nodes
|
||||
};
|
||||
}
|
||||
else if(myToken2.Text == "component")
|
||||
{
|
||||
var pageargs = [];
|
||||
var nodes =[];
|
||||
var name = "Component";
|
||||
//its a component
|
||||
|
||||
EnsureTokenType("OpenParen");
|
||||
while(!TokenTypeIs("CloseParen"))
|
||||
{
|
||||
|
||||
ConsumeIfTokenTypeIs("Comma");
|
||||
if(i < tokens.Length && tokens[i].Type == "Identifier")
|
||||
{
|
||||
pageargs.Add(tokens[i].Text);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
EnsureTokenType("CloseParen");
|
||||
|
||||
while(!TokenTypeIs("ExitSpecial"))
|
||||
{
|
||||
if(TokenTypeIs("Identifier"))
|
||||
{
|
||||
var key = tokens[i++].Text;
|
||||
EnsureTokenType("Equals");
|
||||
if(TokenTypeIs("String"))
|
||||
{
|
||||
var value = tokens[i++].Text;
|
||||
if(key == "name")
|
||||
name = value;
|
||||
else
|
||||
throw $"Unknown property {key}";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Not a string";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Not an identifier";
|
||||
}
|
||||
}
|
||||
|
||||
EnsureTokenType("ExitSpecial");
|
||||
while(NotEnd())
|
||||
{
|
||||
nodes.Add(parse_node());
|
||||
}
|
||||
EnsureEnd();
|
||||
|
||||
|
||||
return {
|
||||
|
||||
Type = "ComponentNode",
|
||||
Arguments = pageargs,
|
||||
Name = name,
|
||||
Nodes = nodes
|
||||
};
|
||||
}
|
||||
else if(myToken2.Text == "code")
|
||||
{
|
||||
var nodes = [];
|
||||
while(!TokenTypeIs("ExitSpecial"))
|
||||
{
|
||||
if(i < tokens.Length)
|
||||
nodes.Add(parse_node());
|
||||
}
|
||||
EnsureTokenType("ExitSpecial");
|
||||
return {
|
||||
Type = "CodeNode",
|
||||
Nodes = nodes
|
||||
};
|
||||
}
|
||||
else if(myToken2.Text == "resource")
|
||||
{
|
||||
var name = "";
|
||||
var route = "";
|
||||
var router_function = "Router";
|
||||
while(!TokenTypeIs("ExitSpecial"))
|
||||
{
|
||||
if(TokenTypeIs("Identifier"))
|
||||
{
|
||||
var key = tokens[i++].Text;
|
||||
EnsureTokenType("Equals");
|
||||
if(TokenTypeIs("String"))
|
||||
{
|
||||
var value = tokens[i++].Text;
|
||||
if(key == "name")
|
||||
name = value;
|
||||
else if(key == "route")
|
||||
route = value;
|
||||
else if(key == "router_function")
|
||||
router_function = value;
|
||||
else
|
||||
throw $"Unknown property {key}";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Not a string";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Not an identifier";
|
||||
}
|
||||
}
|
||||
EnsureTokenType("ExitSpecial");
|
||||
return {
|
||||
Type = "EmbedNode",
|
||||
Name = name,
|
||||
Route = route,
|
||||
RouterFunction = router_function
|
||||
};
|
||||
}
|
||||
else if(myToken2.Text == "expr")
|
||||
{
|
||||
var nodes = [];
|
||||
while(!TokenTypeIs("ExitSpecial"))
|
||||
{
|
||||
if(i < tokens.Length)
|
||||
nodes.Add(parse_node());
|
||||
}
|
||||
|
||||
EnsureTokenType("ExitSpecial");
|
||||
return {
|
||||
Type = "ExprNode",
|
||||
Nodes = nodes
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
EnsureTokenType("ExitSpecial");
|
||||
|
||||
var args = [];
|
||||
|
||||
while(NotEnd())
|
||||
{
|
||||
if(TokenTypeIs("Text"))
|
||||
{
|
||||
EnsureTokenType("Text");
|
||||
}
|
||||
else if(TokenTypeIs("EnterSpecial"))
|
||||
{
|
||||
EnsureTokenType("EnterSpecial");
|
||||
|
||||
var myToken3 = tokens[i++];
|
||||
|
||||
if(myToken3.Type == "Identifier")
|
||||
{
|
||||
if(myToken3.Text == "arg")
|
||||
{
|
||||
var nodes = [];
|
||||
while(!TokenTypeIs("ExitSpecial"))
|
||||
{
|
||||
if(i < tokens.Length)
|
||||
nodes.Add(parse_node());
|
||||
}
|
||||
EnsureTokenType("ExitSpecial");
|
||||
|
||||
args.Add({
|
||||
Type = "ArgNode",
|
||||
Nodes = nodes
|
||||
});
|
||||
}
|
||||
else if(myToken3.Text == "arg_component")
|
||||
{
|
||||
var pageargs = [];
|
||||
var nodes =[];
|
||||
//its a component
|
||||
|
||||
EnsureTokenType("OpenParen");
|
||||
while(!TokenTypeIs("CloseParen"))
|
||||
{
|
||||
|
||||
ConsumeIfTokenTypeIs("Comma");
|
||||
if(i < tokens.Length && tokens[i].Type == "Identifier")
|
||||
{
|
||||
pageargs.Add(tokens[i].Text);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
EnsureTokenType("CloseParen");
|
||||
EnsureTokenType("ExitSpecial");
|
||||
while(NotEnd())
|
||||
{
|
||||
nodes.Add(parse_node());
|
||||
}
|
||||
EnsureEnd();
|
||||
|
||||
args.Add({
|
||||
Type = "ArgComponentNode",
|
||||
Arguments = pageargs,
|
||||
Nodes = nodes
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Must be an identifier";
|
||||
}
|
||||
}
|
||||
}
|
||||
EnsureEnd();
|
||||
return {
|
||||
Type = "UseComponentNode",
|
||||
Name = myToken2.Text,
|
||||
Arguments = args
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(myToken.Type == "Identifier")
|
||||
{
|
||||
return {
|
||||
Type = "IdentifierNode",
|
||||
Text = myToken.Text
|
||||
};
|
||||
}
|
||||
else if(myToken.Type == "Text")
|
||||
{
|
||||
return {
|
||||
Type = "TextNode",
|
||||
Text = myToken.Text
|
||||
};
|
||||
}
|
||||
else if(myToken.Type == "Char")
|
||||
{
|
||||
return {
|
||||
Type = "CharNode",
|
||||
Text = $"\'{myToken.Text}\'"
|
||||
};
|
||||
}
|
||||
else if(myToken.Type == "String")
|
||||
{
|
||||
return {
|
||||
Type = "StringNode",
|
||||
Text = $"\"{myToken.Text}\""
|
||||
};
|
||||
}
|
||||
else if(myToken.Type == "Equals" || myToken.Type == "OpenParen" || myToken.Type == "CloseParen" || myToken.Type == "Comma" || myToken.Type == "")
|
||||
{
|
||||
return {
|
||||
Type = $"{myToken.Type}Node"
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var nodes = [];
|
||||
|
||||
while(i < tokens.Length)
|
||||
{
|
||||
nodes.Add(parse_node());
|
||||
}
|
||||
return {
|
||||
Type = "RootNode",
|
||||
Nodes = nodes
|
||||
};
|
||||
}
|
||||
|
||||
func crossmarkupgen(ast)
|
||||
{
|
||||
var page_functions = [];
|
||||
|
||||
var code = "";
|
||||
|
||||
func add_to_page(name, code)
|
||||
{
|
||||
each(var item : page_functions)
|
||||
{
|
||||
if(item.Key == name)
|
||||
{
|
||||
item.Value += code;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
page_functions.Add({
|
||||
Key = name,
|
||||
Value = code
|
||||
});
|
||||
}
|
||||
|
||||
func generate_node(node)
|
||||
{
|
||||
func myescape(txt)
|
||||
{
|
||||
var out = "";
|
||||
each(var c : txt)
|
||||
{
|
||||
var cI = c.ToLong();
|
||||
if(cI < 32 || cI > 126)
|
||||
{
|
||||
out += $"\\x{(cI % 0xFF).ToHexString(2)}";
|
||||
}
|
||||
else if(c == '\"')
|
||||
{
|
||||
out += "\\\"";
|
||||
}
|
||||
else if(c == '\'')
|
||||
{
|
||||
out += "\\\'";
|
||||
}
|
||||
else if(c == '\\')
|
||||
{
|
||||
out += "\\\\";
|
||||
}
|
||||
else {
|
||||
out += c;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if(node.Type == "PageNode")
|
||||
{
|
||||
var code = $"if(ctx.Path == \"{node.Route}\")";
|
||||
code += "{__writer = ctx.OpenResponseStream(); ";
|
||||
each(var a : node.Arguments)
|
||||
{
|
||||
code += $"var {a} = ctx.QueryParams.TryGetFirst(\"{a}\");";
|
||||
}
|
||||
each(var item : node.Nodes)
|
||||
{
|
||||
code += generate_node(item);
|
||||
}
|
||||
code += " __writer.Close(); return true;}";
|
||||
|
||||
add_to_page(node.RouterFunction,code);
|
||||
return "";
|
||||
}
|
||||
else if(node.Type == "EmbedNode")
|
||||
{
|
||||
var code = $"if(ctx.Path == \"{node.Route}\")";
|
||||
code += "{";
|
||||
code += $"ctx.WithMimeType(Net.Http.MimeType(\"{node.Name}\")).SendBytes(embed(\"{node.Name}\"));";
|
||||
code += "return true;";
|
||||
code += "}";
|
||||
}
|
||||
else if(node.Type == "TextNode")
|
||||
{
|
||||
return $"write(\"{myescape(node.Text)}\");";
|
||||
}
|
||||
else if(node.Type == "ExprNode")
|
||||
{
|
||||
var code = "write(";
|
||||
each(var item : node.Nodes)
|
||||
{
|
||||
code += generate_node(item);
|
||||
}
|
||||
code += ");";
|
||||
return code;
|
||||
}
|
||||
else if(node.Type == "CodeNode")
|
||||
{
|
||||
var code = "";
|
||||
each(var item : node.Nodes)
|
||||
{
|
||||
code += generate_node(item);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
else if(node.Type == "ComponentNode")
|
||||
{
|
||||
var code = $"func {node.Name}(write,ctx";
|
||||
|
||||
each(var arg0 : node.Arguments)
|
||||
{
|
||||
code += $",{arg0}";
|
||||
}
|
||||
code += "){";
|
||||
each(var n : node.Nodes)
|
||||
{
|
||||
code += generate_node(n);
|
||||
}
|
||||
code += "}";
|
||||
return code;
|
||||
}
|
||||
else if(node.Type == "UseComponentNode")
|
||||
{
|
||||
var code = $"{node.Name}(write,ctx";
|
||||
each(var arg : node.Arguments)
|
||||
{
|
||||
code += ",";
|
||||
if(arg.Type == "ArgNode")
|
||||
{
|
||||
each(var itm : arg.Nodes)
|
||||
{
|
||||
code += generate_node(itm);
|
||||
}
|
||||
}
|
||||
else if(arg.Type == "ArgComponentNode")
|
||||
{
|
||||
code += "(write,ctx";
|
||||
|
||||
each(var arg0 : arg.Arguments)
|
||||
{
|
||||
code += $",{arg0}";
|
||||
}
|
||||
code += ")=>{";
|
||||
each(var n : arg.Nodes)
|
||||
{
|
||||
code += generate_node(n);
|
||||
}
|
||||
code += "}";
|
||||
}
|
||||
}
|
||||
code += ");";
|
||||
return code;
|
||||
}
|
||||
else if(node.Type == "CharNode")
|
||||
{
|
||||
return $" {node.Text} ";
|
||||
}
|
||||
else if(node.Type == "StringNode")
|
||||
{
|
||||
return $" {node.Text} ";
|
||||
}
|
||||
else if(node.Type == "IdentifierNode")
|
||||
{
|
||||
return $" {node.Text} ";
|
||||
}
|
||||
else if(node.Type == "OpenParenNode")
|
||||
{
|
||||
return " ( ";
|
||||
}
|
||||
else if(node.Type == "CloseParenNode")
|
||||
{
|
||||
return " ) ";
|
||||
}
|
||||
else if(node.Type == "CommaNode")
|
||||
{
|
||||
return " , ";
|
||||
}
|
||||
else if(node.Type == "EqualsNode")
|
||||
{
|
||||
return " = ";
|
||||
}
|
||||
else if(node.Type == "RootNode")
|
||||
{
|
||||
var code = "";
|
||||
|
||||
each(var item : node.Nodes)
|
||||
{
|
||||
if(item.Type != "TextNode")
|
||||
code += generate_node(item);
|
||||
}
|
||||
|
||||
each(var pg : page_functions)
|
||||
{
|
||||
code += $"func {pg.Key}(ctx)";
|
||||
code += "{ ctx.WithMimeType(\"text/html\");";
|
||||
code += "var __writer = null;";
|
||||
|
||||
code += "func write(__text){ __writer.WriteText(__text); }";
|
||||
|
||||
code += pg.Value;
|
||||
|
||||
code += "__writer.Close(); return false;}";
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
return generate_node(ast);
|
||||
}
|
||||
|
||||
func EnumerateFiles(cfg,path)
|
||||
{
|
||||
var txt = "";
|
||||
if(cfg.Project.DirectoryExists(path))
|
||||
each(var file : cfg.Project.EnumeratePaths(path))
|
||||
{
|
||||
if(cfg.Project.RegularFileExists(file))
|
||||
{
|
||||
var f = cfg.Project.OpenFile(file, "rb");
|
||||
var ms = FS.MemoryStream(true);
|
||||
f.CopyTo(ms);
|
||||
txt += ms.GetBytes().ToString();
|
||||
f.Close();
|
||||
ms.Close();
|
||||
}
|
||||
else if(cfg.Project.DirectoryExists(file))
|
||||
{
|
||||
EnumerateFiles(cfg, file);
|
||||
}
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
|
||||
func RunTool(cfg)
|
||||
{
|
||||
var text = "";
|
||||
text += EnumerateFiles(cfg, "/components");
|
||||
text += EnumerateFiles(cfg, "/pages");
|
||||
var c = crossmarkuplexer(text);
|
||||
|
||||
var r = crossmarkupparser(c);
|
||||
|
||||
var src = crossmarkupgen(r);
|
||||
|
||||
cfg.GeneratedSource.Add({Source = src, FileName="markup.tcross"});
|
||||
}
|
||||
Reference in New Issue
Block a user