First Commit

This commit is contained in:
nicco 2018-01-19 18:52:30 +01:00
parent 53574b13a3
commit f234cd8c18
10 changed files with 445 additions and 0 deletions

99
app.js Normal file
View File

@ -0,0 +1,99 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
exports.__esModule = true;
var fs = require("fs");
var S = {
re: {
begin: /{{/,
end: /}}/
},
opt: {
encoding: 'utf-8'
}
};
var LOG_TYPE;
(function (LOG_TYPE) {
LOG_TYPE[LOG_TYPE["Info"] = 0] = "Info";
LOG_TYPE[LOG_TYPE["Warning"] = 1] = "Warning";
LOG_TYPE[LOG_TYPE["Error"] = 2] = "Error";
})(LOG_TYPE || (LOG_TYPE = {}));
function logger(type, msg) {
if (typeof msg === 'object')
msg = JSON.stringify(msg);
var typeString = '';
switch (type) {
case LOG_TYPE.Info:
typeString = 'Info';
break;
case LOG_TYPE.Warning:
typeString = 'Warning';
break;
case LOG_TYPE.Error:
typeString = 'Error';
break;
}
console.log(typeString + ":", msg);
}
function readFile(url) {
return new Promise(function (res, rej) {
fs.readFile(url, function (err, data) {
if (err)
res();
else
res(data.toString());
});
});
}
function test() {
return __awaiter(this, void 0, void 0, function () {
var html;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, readFile('./test.html')];
case 1:
html = _a.sent();
if (html === undefined) {
logger(LOG_TYPE.Error, 'No file found');
return [2 /*return*/];
}
logger(LOG_TYPE.Info, html);
return [2 /*return*/];
}
});
});
}
test();

1
app.js.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":";;AAAA,yBAAwB;AAGxB,MAAM,CAAC,GAAW;IACjB,EAAE,EAAE;QACH,KAAK,EAAE,IAAI;QACX,GAAG,EAAE,IAAI;KACT;IACD,GAAG,EAAE;QACJ,QAAQ,EAAE,OAAO;KACjB;CACD,CAAA;AAED,kBAAkB,GAAW;IAC5B,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC/B,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;YAC9B,EAAE,CAAC,CAAC,GAAG,CAAC;gBACP,GAAG,CAAC,GAAG,CAAC,CAAA;YACT,IAAI;gBACH,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;QACtB,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;AACH,CAAC;AAED,KAAK;IACJ,OAAO,CAAC,GAAG,CAAC,MAAM,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAA;AAC3C,CAAC;AACD,IAAI,EAAE,CAAA"}

83
app.ts Normal file
View File

@ -0,0 +1,83 @@
import * as fs from 'fs'
import * as path from 'path'
import * as util from './util'
import * as parser from './parser'
import { Render, LOG_TYPE, options } from './options'
import { replaceVars, addParts } from './parser';
const cache: Map<string, Render> = new Map()
function logger(type: LOG_TYPE, msg: object | string): void {
if (typeof msg === 'object')
msg = JSON.stringify(msg)
let typeString: string = ''
switch (type) {
case LOG_TYPE.Info:
typeString = 'Info'
break
case LOG_TYPE.Warning:
typeString = 'Warning'
break
case LOG_TYPE.Error:
typeString = 'Error'
break
}
console.log(`${typeString}:`, msg)
}
async function compile(html: string): Promise<Render> {
html = parser.removeComments(html)
const compiled = replaceVars(html)
return {
do(data) {
return addParts(data, compiled)
},
hash: await util.checksum(html, true),
time: Date.now()
}
}
async function render(template_name: string, data?: any): Promise<string | undefined> {
const compiled_path = path.join(options.compiled_dir, template_name) + `.${options.compiled_dir}`
if (!options.caching || !await util.exists(compiled_path)) {
const template_path = path.join(options.template_dir, template_name) + `.${options.template_ext}`
const html = await util.readFile(template_path)
if (html === undefined) {
logger(LOG_TYPE.Error, 'No file found')
return
}
else
cache.set(template_name, await compile(html))
}
const render = cache.get(template_name)
if (render) {
return render.do(data)
}
else
return
}
async function go() {
console.log(await render('test', {
title: 'title',
body: {
p: [
'omg',
{
check: 'let go'
}
]
},
}))
}
go()

59
options.ts Normal file
View File

@ -0,0 +1,59 @@
export const enum LOG_TYPE {
Info,
Warning,
Error,
}
export interface Render {
do: ((data: any) => string)
hash: string
time: number
}
export interface Error {
parse: string
}
export const error: Error = {
parse: 'Parse Error.'
}
interface Options {
encoding: string
caching: boolean
template_dir: string
template_ext: string
compiled_dir: string
compiled_ext: string
}
export const options: Options = {
encoding: 'utf-8',
caching: true,
template_dir: './views',
template_ext: 'html',
compiled_dir: './views',
compiled_ext: 'htmlbin',
}
interface Expressions {
begin: string
ending: string
comment: string
incude: string
if: string
if_else: string
for: string
for_in: string
}
export const re: Expressions = {
begin: '{{',
ending: '}}',
comment: '#',
incude: '>',
if: '?',
if_else: '!',
for: '*',
for_in: 'in',
}

14
package-lock.json generated Normal file
View File

@ -0,0 +1,14 @@
{
"name": "template",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/node": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.3.0.tgz",
"integrity": "sha512-wNBfvNjzsJl4tswIZKXCFQY0lss9nKUyJnG6T94X/eqjRgI2jHZ4evdjhQYBSan/vGtF6XVXPApOmNH2rf0KKw==",
"dev": true
}
}
}

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "template",
"version": "1.0.0",
"description": "Templating Engine",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Templating",
"Engine"
],
"author": "Niccolo Borgioli",
"license": "MIT",
"devDependencies": {
"@types/node": "^9.3.0"
}
}

65
parser.ts Normal file
View File

@ -0,0 +1,65 @@
import { re, error } from "./options";
function getFromData(data: any, name: string): string {
let ret: any = data
for (let i of name.trim().split('.')) {
const array = i.match(/\[\w+\]/)
if (array === null)
ret = ret[i]
else {
const index: string = array[0].slice(1, -1)
const num_index: number = parseInt(index[0])
ret = ret[i.substr(0, array.index)]
ret = num_index === NaN ? ret[index] : ret[num_index]
}
}
return ret
}
export function addParts(data: any, parts: (((data: any) => string) | string)[]): string {
if (parts.length === 0) return ''
const part: string | ((data: any) => string) = parts[0]
return (typeof part === 'string' ? part : part(data)) + addParts(data, parts.slice(1))
}
export function removeComments(html: string): string {
return html.replace(
new RegExp(`${re.begin}${re.comment}(.|\n)*?${re.comment}${re.ending}`, 'g'), '')
}
export function replaceVars(html: string): (((data: any) => string) | string)[] {
const ret: (((data: any) => string) | string)[] = []
let i = html.match(/{{/) // Starting char
while (i !== null) {
if (i.index === undefined)
throw new Error(error.parse)
// Push text before
ret.push(html.substr(0, i.index))
html = html.slice(i.index + i[0].length)
// Get closing tag
const j = html.match(/}}/)
if (j === null || j.index === undefined)
throw new Error(error.parse)
const sub: string = html.substring(0, j.index)
ret.push((data) => {
return getFromData(data, sub)
})
html = html.slice(j.index + j[0].length)
i = html.match(/{{/) // Starting char
}
ret.push(html)
return ret
}

53
tsconfig.json Normal file
View File

@ -0,0 +1,53 @@
{
"compilerOptions": {
/* Basic Options */
"target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation: */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": false, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"exclude": [
"./node_modules"
]
}

36
util.ts Normal file
View File

@ -0,0 +1,36 @@
import * as fs from 'fs'
import * as crypto from 'crypto'
export function readFile(url: string): Promise<string> {
return new Promise(res => {
fs.readFile(url, (err, data) => {
if (err)
throw new Error(`No such file: ${url}`)
else
res(data.toString())
})
})
}
export function exists(url: string): Promise<boolean> {
return new Promise(res => {
fs.exists(url, _ => {
res(_)
})
})
}
export function checksum(url: string, plain = false, alg = 'sha1'): Promise<string> {
return new Promise(res => {
const hash = crypto.createHash(alg)
if (plain) {
res(hash.update(url).digest('hex'))
}
else {
const stream = fs.createReadStream(url)
stream.on('data', data => hash.update(data, 'utf8'))
stream.on('end', _ => { res(hash.digest('hex')) })
}
})
}

17
views/test.html Normal file
View File

@ -0,0 +1,17 @@
<doctype html>
<head>
</head>
<body>
{{# asdfkjashdlkfjashdlkj #}}
<section>
<h2>{{ title }}</h2>
<p>{{ body.p[1].check }}</p>
</section>
</body>
</doctype>