diff --git a/dependencyInjection/ru/framework.js b/dependencyInjection/ru/framework.js index 164fe89..60893f9 100644 --- a/dependencyInjection/ru/framework.js +++ b/dependencyInjection/ru/framework.js @@ -1,57 +1,68 @@ +'use strict'; + // Типы библиотек -var libraries = { - console: 'global', - setTimeout: 'global', - setInterval: 'global', - fs: 'native', - vm: 'native', - path: 'native', - util: 'native', - ncp: 'module', - colors: 'module', - mkdirp: 'module', +let libraries = { + console: 'global', + setTimeout: 'global', + setInterval: 'global', + fs: 'native', + vm: 'native', + path: 'native', + util: 'native', + ncp: 'module', + colors: 'module', + mkdirp: 'module' }; // Ссылки на метаданные загруженных библиотек -var loaded = {}; +let loaded = {}; // Ссылки на загруженные библиотеки -var api = {}; +let api = {}; // Загружаем два системных модуля и после них основное приложение ['fs', 'vm', 'application'].forEach(loadLibrary); // Функция загрузчик function loadLibrary(name, parent) { - if (typeof(parent) !== 'object') parent = { name: 'framework' }; - console.log('Loading dependency: ' + name + ' into ' + parent.name); - var mod = {}; - loaded[name] = mod; - mod.name = name; - mod.type = libraries[name]; - if (mod.type === 'global') { - mod.interface = global[name]; - api[name] = mod.interface; - } else if (mod.type === 'native' || mod.type === 'module') { - mod.interface = require(name); - api[name] = mod.interface; - } else { - mod.type = 'app'; - mod.context = { module: {} }; - mod.context.global = mod.context; - mod.sandbox = api.vm.createContext(mod.context); - mod.config = require('./' + name + '.json'); - mod.fileName = './' + name + '.js'; - api.fs.readFile(mod.fileName, function(err, src) { - mod.script = api.vm.createScript(src, mod.fileName); - mod.script.runInNewContext(mod.sandbox); - mod.interface = mod.sandbox.exports; - api[name] = mod.interface; - if (mod.config.api) { - mod.config.api.forEach(function(item) { - loadLibrary(item, mod); - }); - } + if ( typeof parent !== 'object') { + parent = { name: 'framework' }; + } + + console.log('Loading dependency: ' + name + ' into ' + parent.name); + + let mod = {}; + + loaded[name] = mod; + mod.name = name; + mod.type = libraries[name]; + + if (mod.type === 'global') { + mod.interface = global[name]; + api[name] = mod.interface; + } else if (mod.type === 'native' || mod.type === 'module') { + mod.interface = require(name); + api[name] = mod.interface; + } else { + mod.type = 'app'; + mod.context = { module: {} }; + mod.context.global = mod.context; + mod.sandbox = api.vm.createContext(mod.context); + mod.config = require('./' + name + '.json'); + mod.fileName = './' + name + '.js'; + + api.fs.readFile(mod.fileName, (err, src) => { + mod.script = api.vm.createScript(src, mod.fileName); + mod.script.runInNewContext(mod.sandbox); + mod.interface = mod.sandbox.exports; + api[name] = mod.interface; + + if (mod.config.api) { + mod.config.api.forEach( (item) => { + loadLibrary(item, mod); + }); + } }); } } + diff --git a/interfaceWrapper/en/README.md b/interfaceWrapper/en/README.md deleted file mode 100644 index 0c3fd0b..0000000 --- a/interfaceWrapper/en/README.md +++ /dev/null @@ -1,99 +0,0 @@ -## Description - -Purpose: learn how to wrap the interfaces and make wrapper flexible enough to -wrap unknown interface with just one assumption that callbacks should be last -argument of asynchronous functions. As an example, we have `setTimeout` wrapped -and as a result we will wrap a whole interface of the file system `fs`. - -## Files - -* `framework.js` - small piece of the framework, just to demonstrate wrapper -* `application.js` - small piece of the application for wrapper demonstration - -## How to execute - -From the command line, type: `node application` then `node framework`, compare -output, read code in `framework.js` and `application.js`, try to understand -how wrapper works for `setTimeout`. - -## Tasks - -1. Learn how `setTimeout` is wrapped in example: `framework.js`. Now we will -try to wrap module fs. We can iterate all of its functions by following code: -`for (var key in fs) {...}` and replace its function with wrapped ones. We need -a closure function and it should be universal to wrap any function in fs -interface. The purpose of this example wrapper is to log all calls to the file -system in a file indicating the time, a function name, its arguments, and if -the function has also callback, it is necessary to intercept it too and wrap -this callback, logging callback calls. -This task can be divided into a few steps. -2. Remove `setTimeout` example from `application.js` and replace it with the -following code: - - ```JavaScript - var fileName = './README.md'; - console.log('Application going to read ' + fileName); - fs.readFile(fileName, function(err, src) { - console.log('File ' + fileName + ' size ' + src.length); - }); - ``` -This example contains a call to `fs.readFile`. In next steps we will change the -behavior of the code changing `framework.js` and wrapping all `fs` functions. -Let's run `node framework` and make sure that it reads the file and displays its -length. -3. Next step is preparing function `cloneInterface(interfaceName)` for cloning -all keys from given library into new interface. So we can pass its result -(cloned `fs`) to sandbox instead of `fs`. Cloning function example: - - ```JavaScript - function cloneInterface(anInterface) { - var clone = {}; - for (var key in anInterface) { - clone[key] = anInterface[key]; - } - return clone; - } - ``` -4. After that we can add wrapper `wrapFunction(fnName, fn)` with 2 arguments: -name of the function and link to a function itself. It returns `wrapper` — -closure function. Closure `wrapper` is a newly created function with the help -of functional inheritance, so it will see `fnName`, `fn` in its context. Thus -we can pass all arguments from wrapper into original function as you see in -example: - - ```JavaScript - function wrapFunction(fnName, fn) { - return function wrapper() { - var args = []; - Array.prototype.push.apply(args, arguments); - console.log('Call: ' + fnName); - console.dir(args); - return fn.apply(undefined, args); - } - } - ``` - -5. Now should detect do we have `callback` argument as a last argument of -function call, we can do that by `typeof()` comparing to `function`. If we have -`callback`, we need to wrap it too, so pass ours function instead of `callback` -and then call original `callback` from this function. -6. Then we can call other functions of `fs` interface from `application.js` and -try to run wrapped code. -7. Add timers in `application.js` and make multiple calls working with files. So -we will model a real application random file system access. Then we can collect -some statistics from `framework.js` and print it every 30 seconds. For example, -you can collect following parameters: - - number of function calls, - - number of callbacks, - - average function completion speed, - - average return rate of callbacks, - - total amount of data read from the disk, - - total amount of recorded data, - - average read and write speed, - etc. - -Save your results to github, we will need it in next labs, for example we can -transfer overriden wrapped calls (fs operations) to another process and another -server. In such a way we can create distributed application. - -## Additional tasks diff --git a/interfaceWrapper/en/application.js b/interfaceWrapper/en/application.js deleted file mode 100644 index 004dd97..0000000 --- a/interfaceWrapper/en/application.js +++ /dev/null @@ -1,10 +0,0 @@ -// Print something -console.log('From application global context'); - -// Declare function for timer event -function timerEvent() { - console.log('From application timer event'); -} - -// Create timer -setTimeout(timerEvent, 1000); diff --git a/interfaceWrapper/en/framework.js b/interfaceWrapper/en/framework.js deleted file mode 100644 index 0f76e23..0000000 --- a/interfaceWrapper/en/framework.js +++ /dev/null @@ -1,40 +0,0 @@ -// Wrapping function and interface example - -var fs = require('fs'), - vm = require('vm'); - -// Create a hash for application sandbox -var context = { - module: {}, - console: console, - // Forward link to fs API into sandbox - fs: fs, - // Wrapper for setTimeout in sandbox - setTimeout: function(callback, timeout) { - // Logging all setTimeout calls - console.log( - 'Call: setTimeout, ' + - 'callback function: ' + callback.name + ', ' + - 'timeout: ' + timeout - ); - setTimeout(function() { - // Logging timer events before application event - console.log('Event: setTimeout, before callback'); - // Calling user-defined timer event - callback(); - console.log('Event: setTimeout, after callback'); - }, timeout); - } -}; - -// Turn hash into context -context.global = context; -var sandbox = vm.createContext(context); - -// Read an application source code from the file -var fileName = './application.js'; -fs.readFile(fileName, function(err, src) { - // Run an application in sandboxed context - var script = vm.createScript(src, fileName); - script.runInNewContext(sandbox); -}); diff --git a/interfaceWrapper/ru/application.js b/interfaceWrapper/ru/application.js index 90d20d9..3c79a11 100644 --- a/interfaceWrapper/ru/application.js +++ b/interfaceWrapper/ru/application.js @@ -1,10 +1,57 @@ +'use strict'; + +//const fs = global.fs || require('fs'); + +const fs = require('fs'); + // Вывод из глобального контекста модуля -console.log('From application global context'); +console.log(' From application global context'); // Объявляем функцию для события таймера function timerEvent() { - console.log('From application timer event'); + console.log(' From application timer event'); } // Устанавливаем функцию на таймер -setTimeout(timerEvent, 1000); +//setTimeout(timerEvent, 1500); + + +let readme = './README.md'; +let sequental = './sequental.js'; + +/* +fs.readFile(readme, 'utf8', (err, src) => { + if (err) { + console.error(err); + + return; + } + + console.log(' File "%s" Size "%d"', readme, src.length); +}); +*/ + +//console.log(fs.constants); +/* +fs.readFile(sequental, 'utf8', (err, src) => { + if (err) { + console.error(err); + + return; + } + + console.log(' File "%s" Size "%d"', sequental, src.length); +}); +*/ + +const fourtyTwo = 42; + +function getParams(data, callback) { + callback(data); +} + +module.exports = { + getParams, + fourtyTwo +}; + diff --git a/interfaceWrapper/ru/framework.js b/interfaceWrapper/ru/framework.js index 25b3fce..d9e4da8 100644 --- a/interfaceWrapper/ru/framework.js +++ b/interfaceWrapper/ru/framework.js @@ -1,40 +1,193 @@ -// Пример оборачивания функции в песочнице - -var fs = require('fs'), - vm = require('vm'); - -// Объявляем хеш из которого сделаем контекст-песочницу -var context = { - module: {}, - console: console, - // Помещаем ссылку на fs API в песочницу - fs: fs, - // Оборачиваем функцию setTimeout в песочнице - setTimeout: function(callback, timeout) { - // Добавляем поведение при вызове setTimeout - console.log( - 'Call: setTimeout, ' + - 'callback function: ' + callback.name + ', ' + - 'timeout: ' + timeout - ); - setTimeout(function() { - // Добавляем поведение при срабатывании таймера - console.log('Event: setTimeout, before callback'); - // Вызываем функцию пользователя на событии таймера - callback(); - console.log('Event: setTimeout, after callback'); - }, timeout); - } -}; - -// Преобразовываем хеш в контекст -context.global = context; -var sandbox = vm.createContext(context); - -// Читаем исходный код приложения из файла -var fileName = './application.js'; -fs.readFile(fileName, function(err, src) { - // Запускаем код приложения в песочнице - var script = vm.createScript(src, fileName); - script.runInNewContext(sandbox); -}); +'use strict'; + +const fs = require('fs'); +const vm = require('vm'); +const stat = require('./stat'); +const wrap = require('./wrap'); +const time = require('./timeLogger'); + +let fileName = process.argv[2]; + +let context = {}; + +if (fileName) { + context = { + module: {}, + console: {} + }; + + /* + setInterval( () => { + stat.show(); + }, 3000); + */ + + context.fs = wrap.cloneAPI(fs); + + context.setTimeout = wrap.cloneFn('setTimeout', setTimeout); + context.clearTimeout = wrap.cloneFn('clearTimeout', clearTimeout); + + context.console.log = wrap.cloneFn('console.log', logMessageFile); + + context.require = wrap.cloneFn('require', require); + + // Преобразовываем хеш в контекст + context.global = context; + + let sandbox = vm.createContext(context); + + fs.readFile(fileName, (err, src) => { + if (err) { + throw err; + } + + let scriptOpts = { + fileName, + displayErrors: true + }; + + let script = vm.createScript(src, fileName); + script.runInNewContext(sandbox); + + let app = sandbox.module.exports; + + //showAppExp(app); + + //printFn(app.getParams); + + //printSandbox(); + + printDiffGlobal(global, context); + }); +} + +function printSandbox() { + printAppExp(context); +} + +function printFn(fn) { + console.log( + ' Name: "%s"\n Args Len: %d\n code: %s\n', + fn.name, fn.length, fn.toString() + ); +} + +function printAppExp(obj) { + console.log('Application Export'); + + for (let key in obj) { + let hasProperty = obj.hasOwnProperty(key); + + if (hasProperty) { + console.log(' %s: type: %s', key, typeof obj[key]); + } + } +} + +function logMessageFile(...args) { + let filename = './log'; + + let log = (args) => { + return logMessage(args); + }; + + fs.appendFile(filename, log(), (err) => { + if (err) { + throw err; + } + + console.log('\n Data was Append to "%s"', filename); + }); +} + +function logMessage(...args) { + let log = time.showTime() + ' '; + + let message = 'hello from framework'; + + if (message) { + log += 'Msg: ' + message; + } + + if (fileName) { + log += '; App Name: ' + fileName; + } + + log += '\n' + args; + + console.log(log); + + return log; +} + +function logFn(app, name) { + let fn = app[name]; + + console.log('Code: ', fn.toString() ); + console.log('Args Number: ', fn.length); +} + +function printDiffGlobal(global, context) { + let result = {}; + let added = []; + let deleted = []; + + let gName = Object.getOwnPropertyNames(global).sort(); + let sName = Object.getOwnPropertyNames(context).sort(); + + let glen = gName.length; + let slen = sName.length; + + for (let i = 0; i < slen; i += 1) { + let key = sName[i]; + + for (var j = 0, isExist = true; isExist && j < glen; j += 1) { + if (key === gName[j]) { + isExist = false; + } + } + + if (j === glen) { + added.push(key); + } + } + + result.added = added; + + for (let k = 0; k < glen; k += 1) { + let key = gName[k]; + + for (var l = 0, isNoExist = true; isNoExist && l < slen; l += 1) { + if (key === sName[l]) { + isNoExist = false; + } + } + + if (l === slen) { + deleted.push(key); + } + } + + result.deleted = deleted; + + console.log(result); +} + +function useUtil(app) { + const util = app.require('util'); + + console.log( util.format('Hello %s', 'util') ); + + //app.console.log( util.inspect({a: '42', rt: 89}) ); + + const fs = app.require('fs'); + + fs.writeFile('out.txt', 'Hello Node.js', (err) => { + if (err) { + throw err; + } + + console.log('It\'s saved!\n'); + }); +} + diff --git a/interfaceWrapper/ru/stat.js b/interfaceWrapper/ru/stat.js new file mode 100644 index 0000000..a8082c6 --- /dev/null +++ b/interfaceWrapper/ru/stat.js @@ -0,0 +1,97 @@ +'use strict'; + +let stat = { + modelFunc: class StatFunc { + constructor() { + this.call = 0; + this.startTime = []; + this.callTime = []; + this.callbackTime = []; + } + + getCall() { + return this.call; + } + + getStartTime() { + return this.callTime; + } + + getCallTime() { + return this.callTime; + } + + getCallbackTime() { + return this.callbackTime; + } + }, + + fns: {}, + + show() { + let fns = this.fns; + let fnNames = Object.getOwnPropertyNames(fns); + + for (var i = 0, len = fnNames.length; i < len; i += 1) { + let name = fnNames[i]; + let data = this.averageTime(fns[name]); + + this.showStatFunc(name, data); + } + }, + + averageTime(fn) { + let callDuration = []; + let callbackDuration = []; + + let call = fn.getCall(); + let startTime = fn.getStartTime(); + let callTime = fn.getCallTime(); + let callbackTime = fn.getCallbackTime(); + + for (let i = 0, len = startTime.length; i < len; i += 1) { + callDuration[i] = (callTime[i] - startTime[i]) / call; + callbackDuration[i] = (callbackTime[i] - startTime[i]) / call; + } + + return { + call, + callDuration: callDuration.reduce( (prev, cur) => prev + cur, 0), + callbackDuration: callbackDuration.reduce( (prev, cur) => prev + cur, 0) + } + }, + + showStatFunc(name, {call, callDuration, callbackDuration}) { + console.log('\n "%s":', name); + console.log(' Calls: %d', call); + console.log(' Calls Duration: %d', callDuration); + console.log(' Callbacks Duration: %d', callbackDuration); + }, + + setName(name) { + let isExistFn = this.fns[name]; + + if (!isExistFn) { + this.fns[name] = new this.modelFunc(name); + } + }, + + setCall(name) { + this.fns[name]['call'] += 1; + }, + + setStartTime(name, ms) { + this.fns[name]['startTime'].push(ms); + }, + + setCbTime(name, ms) { + this.fns[name]['callbackTime'].push(ms); + }, + + setCallTime(name, ms) { + this.fns[name]['callTime'].push(ms); + } +}; + +module.exports = stat; + diff --git a/interfaceWrapper/ru/timeLogger.js b/interfaceWrapper/ru/timeLogger.js new file mode 100644 index 0000000..9677bcf --- /dev/null +++ b/interfaceWrapper/ru/timeLogger.js @@ -0,0 +1,18 @@ +'use strict'; + +let api = {}; + +function showTime() { + let now = new Date(); + + let hour = now.getHours(); + let min = now.getMinutes(); + let sec = now.getSeconds(); + let msec = now.getMilliseconds(); + + return ' ' + hour + ':' + min + ':' + sec + ':' + msec; +}; + +module.exports = { + showTime +}; diff --git a/interfaceWrapper/ru/wrap.js b/interfaceWrapper/ru/wrap.js new file mode 100644 index 0000000..22a81de --- /dev/null +++ b/interfaceWrapper/ru/wrap.js @@ -0,0 +1,119 @@ +'use strict'; + +const stat = require('./stat'); +const time = require('./timeLogger'); +const fs = require('fs'); + +let api = {}; + +function writeToFile(data) { + let call = './call'; + + fs.appendFile(call, data); +} + +function cloneFn(fnName, fn) { + let wrapFn; + + let type = typeof fn; + + wrapFn = wrapType[type](fnName, fn); + + return wrapFn; +} + +function cloneAPI(api) { + let clone = {}; + + for (let key in api) { + let type = typeof api[key]; + + clone[key] = wrapType[type](key, api[key]); + } + + return clone; +} + +let wrapType = { + 'object' : function wrapObject(objName, obj) { + return function wrapper() { + let log = '\n Call: ' + objName + ' ' + + time.showTime() + + ' Object: ' + obj + '\n'; + + //console.log(log); + writeToFile(log); + } + }, + + 'number' : function wrapNumber(numName, number) { + return function wrapper() { + let log = '\n Call: ' + numName + ' ' + + time.showTime() + + ' Number: ' + number + '\n'; + + //console.log(log); + writeToFile(log); + } + }, + + 'function' : function wrapFunction(fnName, fn) { + return function wrapper(...args) { + let log = '\n Call: ' + fnName + ' ' + + time.showTime() + + ' Args: ' + args + '\n'; + + //console.log(log); + writeToFile(log); + + stat.setName(fnName); + stat.setCall(fnName); + stat.setStartTime(fnName, Date.now() ); + + hasCallback(args, fnName); + + return fn.apply(this, args); + }; + } +}; + +function hasCallback(args, fnName) { + let isFn = function isFn(obj) { + return typeof obj === 'function' + }; + + let obj = args.find(isFn); + let objIndex = args.findIndex(isFn); + + if (obj) { + args[objIndex] = wrapCallback(obj, fnName); + } + + stat.setCallTime(fnName, Date.now() ); +} + +function wrapCallback(fn, fnName) { + return (err, data) => { + let logBefore = ' Before Callback\n' + + time.showTime(); + + let logAfter = ' After Callback\n'; + + //console.log(logBefore); + writeToFile(logBefore); + + fn(err, data); + + //console.log(logAfter); + writeToFile(logAfter); + + stat.setCbTime(fnName, Date.now() ); + }; +} + + +module.exports = { + cloneAPI, + cloneFn +}; + diff --git a/sandboxedModule/en/README.md b/sandboxedModule/en/README.md deleted file mode 100644 index 0541ff1..0000000 --- a/sandboxedModule/en/README.md +++ /dev/null @@ -1,67 +0,0 @@ -## Description - -Purpose: learn how to create sandboxed context for modues to separate them and -minimize cross-modules code coupling, extracting at least two abstraction layers -(applied and system) and how to execute applied code in virtual environment, -changing its behavior using IoC from system layer. - -## Files - -* `framework.js` - small piece of the framework, just to demonstrate IoC -* `application.js` - small piece of the application, also for IoC demonstration - -## How to execute - -From the command line, type: `node ./framework.js` or `node framework` - -## Tasks - -You may select at least one of the following tasks, make a fork of this -repository and implement your solution there. If those tasks are simple -for somebody, please see additional tasks below. - -1. Add `setTimeout` and `setInterval` to the application context and use them -printing something from the timer function using console.log() - -2. Inject a link to `util` library into the application context and make a few -calls to its functions from applied code - -3. Implement the ability to run different applications inside framework, using -command line option, e.g.: `node framework ` - -4. Wrap or intercept `console.log()` call to add more info into console output -in the following format: `