├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── JavaScript ├── 1-extract-metadata │ ├── 1-filter-simple.js │ └── 2-metaprogramming.js ├── 2-level-up │ ├── 1-metaprogramming.js │ ├── 2-metameta.js │ ├── 3-optimized.js │ ├── 4-metedata.jstp │ ├── file1.json │ ├── file2.json │ └── file3.json ├── 3-introspection │ └── wcl.js └── 4-class-factory │ ├── class-inheritance-metaprogramming.js │ └── class-inheritance-simple.js ├── LICENSE └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": 2020 10 | }, 11 | "globals": { 12 | "BigInt": true 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 2 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "unix" 22 | ], 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-loop-func": [ 32 | "error" 33 | ], 34 | "block-spacing": [ 35 | "error", 36 | "always" 37 | ], 38 | "camelcase": [ 39 | "error" 40 | ], 41 | "eqeqeq": [ 42 | "error", 43 | "always" 44 | ], 45 | "strict": [ 46 | "error", 47 | "global" 48 | ], 49 | "brace-style": [ 50 | "error", 51 | "1tbs", 52 | { 53 | "allowSingleLine": true 54 | } 55 | ], 56 | "comma-style": [ 57 | "error", 58 | "last" 59 | ], 60 | "comma-spacing": [ 61 | "error", 62 | { 63 | "before": false, 64 | "after": true 65 | } 66 | ], 67 | "eol-last": [ 68 | "error" 69 | ], 70 | "func-call-spacing": [ 71 | "error", 72 | "never" 73 | ], 74 | "key-spacing": [ 75 | "error", 76 | { 77 | "beforeColon": false, 78 | "afterColon": true, 79 | "mode": "minimum" 80 | } 81 | ], 82 | "keyword-spacing": [ 83 | "error", 84 | { 85 | "before": true, 86 | "after": true, 87 | "overrides": { 88 | "function": { 89 | "after": false 90 | } 91 | } 92 | } 93 | ], 94 | "max-len": [ 95 | "error", 96 | { 97 | "code": 80, 98 | "ignoreUrls": true 99 | } 100 | ], 101 | "max-nested-callbacks": [ 102 | "error", 103 | { 104 | "max": 7 105 | } 106 | ], 107 | "new-cap": [ 108 | "error", 109 | { 110 | "newIsCap": true, 111 | "capIsNew": false, 112 | "properties": true 113 | } 114 | ], 115 | "new-parens": [ 116 | "error" 117 | ], 118 | "no-lonely-if": [ 119 | "error" 120 | ], 121 | "no-trailing-spaces": [ 122 | "error" 123 | ], 124 | "no-unneeded-ternary": [ 125 | "error" 126 | ], 127 | "no-whitespace-before-property": [ 128 | "error" 129 | ], 130 | "object-curly-spacing": [ 131 | "error", 132 | "always" 133 | ], 134 | "operator-assignment": [ 135 | "error", 136 | "always" 137 | ], 138 | "operator-linebreak": [ 139 | "error", 140 | "after" 141 | ], 142 | "semi-spacing": [ 143 | "error", 144 | { 145 | "before": false, 146 | "after": true 147 | } 148 | ], 149 | "space-before-blocks": [ 150 | "error", 151 | "always" 152 | ], 153 | "space-before-function-paren": [ 154 | "error", 155 | { 156 | "anonymous": "never", 157 | "named": "never", 158 | "asyncArrow": "always" 159 | } 160 | ], 161 | "space-in-parens": [ 162 | "error", 163 | "never" 164 | ], 165 | "space-infix-ops": [ 166 | "error" 167 | ], 168 | "space-unary-ops": [ 169 | "error", 170 | { 171 | "words": true, 172 | "nonwords": false, 173 | "overrides": { 174 | "typeof": false 175 | } 176 | } 177 | ], 178 | "no-unreachable": [ 179 | "error" 180 | ], 181 | "no-global-assign": [ 182 | "error" 183 | ], 184 | "no-self-compare": [ 185 | "error" 186 | ], 187 | "no-unmodified-loop-condition": [ 188 | "error" 189 | ], 190 | "no-constant-condition": [ 191 | "error", 192 | { 193 | "checkLoops": false 194 | } 195 | ], 196 | "no-console": [ 197 | "off" 198 | ], 199 | "no-useless-concat": [ 200 | "error" 201 | ], 202 | "no-useless-escape": [ 203 | "error" 204 | ], 205 | "no-shadow-restricted-names": [ 206 | "error" 207 | ], 208 | "no-use-before-define": [ 209 | "error", 210 | { 211 | "functions": false 212 | } 213 | ], 214 | "arrow-parens": [ 215 | "error", 216 | "always" 217 | ], 218 | "arrow-body-style": [ 219 | "error", 220 | "as-needed" 221 | ], 222 | "arrow-spacing": [ 223 | "error" 224 | ], 225 | "no-confusing-arrow": [ 226 | "error", 227 | { 228 | "allowParens": true 229 | } 230 | ], 231 | "no-useless-computed-key": [ 232 | "error" 233 | ], 234 | "no-useless-rename": [ 235 | "error" 236 | ], 237 | "no-var": [ 238 | "error" 239 | ], 240 | "object-shorthand": [ 241 | "error", 242 | "always" 243 | ], 244 | "prefer-arrow-callback": [ 245 | "error" 246 | ], 247 | "prefer-const": [ 248 | "error" 249 | ], 250 | "prefer-numeric-literals": [ 251 | "error" 252 | ], 253 | "prefer-rest-params": [ 254 | "error" 255 | ], 256 | "prefer-spread": [ 257 | "error" 258 | ], 259 | "rest-spread-spacing": [ 260 | "error", 261 | "never" 262 | ], 263 | "template-curly-spacing": [ 264 | "error", 265 | "never" 266 | ] 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /JavaScript/1-extract-metadata/1-filter-simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Dataset 4 | 5 | const names = [ 6 | 'Marcus Aurelius Antoninus Augustus', 7 | 'Darth Vader', 8 | 'Victor Michailovich Glushkov', 9 | 'Gottfried Wilhelm von Leibniz', 10 | 'Mao Zedong', 11 | 'Vladimir Sergeevich Soloviov', 12 | 'Ibn Arabi', 13 | 'Lev Nikolayevich Tolstoy', 14 | 'Muammar Muhammad Abu Minyar al-Gaddafi', 15 | 'Rene Descartes', 16 | 'Fyodor Mikhailovich Dostoyevsky', 17 | 'Benedito de Espinosa', 18 | ]; 19 | 20 | // Filter mixed with conditions 21 | 22 | const filter = (names) => { 23 | const result = []; 24 | for (let i = 0; i < names.length; i++) { 25 | const name = names[i]; 26 | if ( 27 | name.length >= 10 && name.length <= 200 && 28 | name.includes('Mich') && 29 | name.startsWith('V') && 30 | name.endsWith('ov') && 31 | !( 32 | name.length >= 50 && name.length <= 65 && 33 | name.includes('Abu') && 34 | name.startsWith('Lev') && 35 | name.endsWith('iov') 36 | ) 37 | ) result.push(name); 38 | } 39 | return result; 40 | }; 41 | 42 | // Usage 43 | 44 | const result = filter(names); 45 | console.dir({ names }); 46 | console.dir({ result }); 47 | -------------------------------------------------------------------------------- /JavaScript/1-extract-metadata/2-metaprogramming.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Dataset 4 | 5 | const names = [ 6 | 'Marcus Aurelius Antoninus Augustus', 7 | 'Darth Vader', 8 | 'Victor Michailovich Glushkov', 9 | 'Gottfried Wilhelm von Leibniz', 10 | 'Mao Zedong', 11 | 'Vladimir Sergeevich Soloviov', 12 | 'Ibn Arabi', 13 | 'Lev Nikolayevich Tolstoy', 14 | 'Muammar Muhammad Abu Minyar al-Gaddafi', 15 | 'Rene Descartes', 16 | 'Fyodor Mikhailovich Dostoyevsky', 17 | 'Benedito de Espinosa', 18 | ]; 19 | 20 | // Metadata 21 | 22 | const conditions = { 23 | length: [10, 200], 24 | contains: 'Mich', 25 | starts: 'V', 26 | ends: 'ov', 27 | not: { 28 | length: [50, 65], 29 | contains: 'Abu', 30 | starts: 'Lev', 31 | ends: 'iov' 32 | } 33 | }; 34 | 35 | // Metaprogramming 36 | 37 | const filter = (names, conditions) => { 38 | const operations = {}; 39 | const check = (s, conditions) => { 40 | let valid = true; 41 | for (const key in conditions) { 42 | valid = valid && operations[key](s, conditions[key]); 43 | } 44 | return valid; 45 | }; 46 | Object.assign(operations, { 47 | length: (s, v) => s.length >= v[0] && s.length <= v[1], 48 | contains: (s, v) => s.includes(v), 49 | starts: (s, v) => s.startsWith(v), 50 | ends: (s, v) => s.endsWith(v), 51 | not: (s, v) => !check(s, v), 52 | }); 53 | return names.filter((s) => check(s, conditions)); 54 | }; 55 | 56 | // Usage 57 | 58 | const result = filter(names, conditions); 59 | console.dir({ names }); 60 | console.dir({ result }); 61 | -------------------------------------------------------------------------------- /JavaScript/2-level-up/1-metaprogramming.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const http = require('http'); 5 | 6 | // Parse duration to seconds 7 | // Example: duration('1d 10h 7m 13s') 8 | const duration = (s) => { 9 | if (typeof s === 'number') return s; 10 | let result = 0; 11 | if (typeof s === 'string') { 12 | const days = s.match(/(\d+)\s*d/); 13 | const hours = s.match(/(\d+)\s*h/); 14 | const minutes = s.match(/(\d+)\s*m/); 15 | const seconds = s.match(/(\d+)\s*s/); 16 | if (days) result += parseInt(days[1]) * 86400; 17 | if (hours) result += parseInt(hours[1]) * 3600; 18 | if (minutes) result += parseInt(minutes[1]) * 60; 19 | if (seconds) result += parseInt(seconds[1]); 20 | return result * 1000; 21 | } 22 | }; 23 | 24 | // Transports 25 | 26 | // TODO: stub to be implemented 27 | const transport = { 28 | get: http.get, 29 | post: () => {}, 30 | put: () => {}, 31 | }; 32 | 33 | // Metadata 34 | 35 | const tasks = [ 36 | { interval: 5000, 37 | get: 'http://127.0.0.1/api/method1.json', 38 | save: 'file1.json' }, 39 | { interval: '8s', 40 | get: 'http://127.0.0.1/api/method2.json', 41 | put: 'http://127.0.0.1/api/method4.json', 42 | save: 'file2.json' }, 43 | { interval: '7s', 44 | get: 'http://127.0.0.1/api/method3.json', 45 | post: 'http://127.0.0.1/api/method5.json' }, 46 | { interval: '4s', 47 | load: 'file1.json', 48 | put: 'http://127.0.0.1/api/method6.json' }, 49 | { interval: '9s', 50 | load: 'file2.json', 51 | post: 'http://127.0.0.1/api/method7.json', 52 | save: 'file1.json' }, 53 | { interval: '3s', 54 | load: 'file1.json', 55 | save: 'file3.json' } 56 | ]; 57 | 58 | // Metaprogram 59 | 60 | const iterate = (tasks) => { 61 | const closureTask = (task) => () => { 62 | console.dir(task); 63 | let source; 64 | if (task.get) source = transport.get(task.get); 65 | if (task.load) source = fs.createReadStream(task.load); 66 | if (task.save) source.pipe(fs.createWriteStream(task.save)); 67 | if (task.post) source.pipe(transport.post(task.post)); 68 | if (task.put) source.pipe(transport.put(task.put)); 69 | }; 70 | for (const task of tasks) { 71 | setInterval(closureTask(task), duration(task.interval)); 72 | } 73 | }; 74 | 75 | // Usage 76 | 77 | iterate(tasks); 78 | -------------------------------------------------------------------------------- /JavaScript/2-level-up/2-metameta.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const request = require('request'); 5 | 6 | // Utilities 7 | 8 | const DURATION_UNITS = { 9 | d: 86400, // days 10 | h: 3600, // hours 11 | m: 60, // minutes 12 | s: 1, // seconds 13 | }; 14 | 15 | // Parse duration to seconds 16 | // s duration syntax 17 | // Returns: milliseconds 18 | // Example: duration('1d 10h 7m 13s') 19 | const duration = (s) => { 20 | if (typeof s === 'number') return s; 21 | if (typeof s !== 'string') return 0; 22 | let result = 0; 23 | const parts = s.split(' '); 24 | for (const part of parts) { 25 | const unit = part.slice(-1); 26 | const value = parseInt(part.slice(0, -1)); 27 | const mult = DURATION_UNITS[unit]; 28 | if (!isNaN(value)) result += value * mult; 29 | } 30 | return result * 1000; 31 | }; 32 | 33 | // Metadata 34 | 35 | const tasks = [ 36 | { interval: 5000, 37 | get: 'http://127.0.0.1/api/method1.json', 38 | save: 'file1.json' }, 39 | { interval: '8s', 40 | get: 'http://127.0.0.1/api/method2.json', 41 | put: 'http://127.0.0.1/api/method4.json', 42 | save: 'file2.json' }, 43 | { interval: '7s', 44 | get: 'http://127.0.0.1/api/method3.json', 45 | post: 'http://127.0.0.1/api/method5.json' }, 46 | { interval: '4s', 47 | load: 'file1.json', 48 | put: 'http://127.0.0.1/api/method6.json' }, 49 | { interval: '9s', 50 | load: 'file2.json', 51 | post: 'http://127.0.0.1/api/method7.json', 52 | save: 'file1.json' }, 53 | { interval: '3s', 54 | load: 'file1.json', 55 | save: 'file3.json' }, 56 | ]; 57 | 58 | // Metaprogramming 59 | 60 | const iterate = (tasks) => { 61 | 62 | // Configuration metadata 63 | const sources = { 64 | get: request.get, 65 | load: fs.createReadStream 66 | }; 67 | const destinations = { 68 | save: fs.createWriteStream, 69 | post: request.post, 70 | put: request.put 71 | }; 72 | 73 | // Abcstract logic 74 | const closureTask = (task) => () => { 75 | console.dir(task); 76 | let source; 77 | for (const key in sources) { 78 | if (task[key]) source = sources[key](task[key]); 79 | } 80 | for (const key in destinations) { 81 | if (task[key]) source.pipe(destinations[key](task[key])); 82 | } 83 | }; 84 | 85 | for (const task of tasks) { 86 | setInterval(closureTask(task), duration(task.interval)); 87 | } 88 | }; 89 | 90 | // Usage 91 | 92 | iterate(tasks); 93 | -------------------------------------------------------------------------------- /JavaScript/2-level-up/3-optimized.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const request = require('request'); 5 | 6 | // Utilities 7 | 8 | const DURATION_UNITS = { 9 | d: 86400, // days 10 | h: 3600, // hours 11 | m: 60, // minutes 12 | s: 1, // seconds 13 | }; 14 | 15 | // Parse duration to seconds 16 | // s duration syntax 17 | // Returns: milliseconds 18 | // Example: duration('1d 10h 7m 13s') 19 | const duration = (s) => { 20 | if (typeof s === 'number') return s; 21 | if (typeof s !== 'string') return 0; 22 | let result = 0; 23 | const parts = s.split(' '); 24 | for (const part of parts) { 25 | const unit = part.slice(-1); 26 | const value = parseInt(part.slice(0, -1)); 27 | const mult = DURATION_UNITS[unit]; 28 | if (!isNaN(value)) result += value * mult; 29 | } 30 | return result * 1000; 31 | }; 32 | 33 | // Metadata 34 | 35 | const tasks = [ 36 | { interval: 5000, 37 | get: 'http://127.0.0.1/api/method1.json', 38 | save: 'file1.json' }, 39 | { interval: '8s', 40 | get: 'http://127.0.0.1/api/method2.json', 41 | put: 'http://127.0.0.1/api/method4.json', 42 | save: 'file2.json' }, 43 | { interval: '7s', 44 | get: 'http://127.0.0.1/api/method3.json', 45 | post: 'http://127.0.0.1/api/method5.json' }, 46 | { interval: '4s', 47 | load: 'file1.json', 48 | put: 'http://127.0.0.1/api/method6.json' }, 49 | { interval: '9s', 50 | load: 'file2.json', 51 | post: 'http://127.0.0.1/api/method7.json', 52 | save: 'file1.json' }, 53 | { interval: '3s', 54 | load: 'file1.json', 55 | save: 'file3.json' }, 56 | ]; 57 | 58 | // Metaprogramming 59 | 60 | const iterate = (tasks) => { 61 | 62 | // Configuration metadata 63 | const sources = { 64 | get: request.get, 65 | load: fs.createReadStream 66 | }; 67 | const destinations = { 68 | save: fs.createWriteStream, 69 | post: request.post, 70 | put: request.put 71 | }; 72 | 73 | // Abcstract logic 74 | const closureTask = (task) => () => { 75 | console.dir(task); 76 | let source; 77 | for (const key in sources) { 78 | const value = task[key]; 79 | if (value) source = sources[key](value); 80 | } 81 | for (const key in destinations) { 82 | const value = task[key]; 83 | if (value) { 84 | const dest = destinations[key](value); 85 | source.pipe(dest); 86 | } 87 | } 88 | }; 89 | 90 | for (const task of tasks) { 91 | const interval = duration(task.interval); 92 | setInterval(closureTask(task), interval); 93 | } 94 | }; 95 | 96 | // Usage 97 | 98 | iterate(tasks); 99 | -------------------------------------------------------------------------------- /JavaScript/2-level-up/4-metedata.jstp: -------------------------------------------------------------------------------- 1 | { 2 | counters: { success: 0, error: 0, wrong: 0 }, 3 | tasks: [ 4 | { interval: '5s', get: 'http://127.0.0.1/api/method1.json', 5 | save: 'file1.json' }, 6 | { interval: '8s', get: 'http://127.0.0.1/api/method2.json', 7 | put: 'http://127.0.0.1/api/method4.json', save: 'file2.json' }, 8 | { interval: '7s', get: 'http://127.0.0.1/api/method3.json', 9 | post: 'http://127.0.0.1/api/method5.json' }, 10 | { interval: '4s', load: 'file1.json', 11 | put: 'http://127.0.0.1/api/method6.json' }, 12 | { interval: '9s', load: 'file2.json', 13 | post: 'http://127.0.0.1/api/method7.json', save: 'file1.json' }, 14 | { interval: '3s', load: 'file1.json', 15 | save: 'file3.json' }, 16 | ], 17 | error: (err, request) => console.log('Error'), 18 | success: (err, request) => counters.success++, 19 | } 20 | -------------------------------------------------------------------------------- /JavaScript/2-level-up/file1.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HowProgrammingWorks/Metaprogramming/2cdce42f21c9202cb6f44bb519ef63a7b0a94648/JavaScript/2-level-up/file1.json -------------------------------------------------------------------------------- /JavaScript/2-level-up/file2.json: -------------------------------------------------------------------------------- 1 | {"statusCode":404} -------------------------------------------------------------------------------- /JavaScript/2-level-up/file3.json: -------------------------------------------------------------------------------- 1 | {"statusCode":404} -------------------------------------------------------------------------------- /JavaScript/3-introspection/wcl.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (wcl => { 4 | 5 | wcl.AjaxAPI = function(methods) { // params: { method: { get/post:url }, ... } 6 | const api = {}; 7 | api.request = function(apiMethod, params, callback) { 8 | let err = null, requestParams = this.methods[apiMethod]; 9 | if (requestParams) { 10 | let httpMethod, url; 11 | if (requestParams.get) { 12 | httpMethod = 'GET'; 13 | url = requestParams.get; 14 | } 15 | if (requestParams.post) { 16 | httpMethod = 'POST'; 17 | url = requestParams.post; 18 | } 19 | if (httpMethod) { 20 | wcl.request(httpMethod, url, params, true, callback); 21 | return; 22 | } else { 23 | err = new Error('DataSource error: HTTP method is not specified'); 24 | } 25 | } else { 26 | err = new Error('DataSource error: AJAX method is not specified'); 27 | } 28 | callback(err, null); 29 | }; 30 | api.init = function(methods) { 31 | api.methods = methods; 32 | for (var method in api.methods) { 33 | (function() { 34 | const apiMethod = method; 35 | if (apiMethod === 'introspect') { 36 | api[apiMethod] = function(params, callback) { 37 | api.request(apiMethod, params, (err, data) => { 38 | api.init(data); 39 | callback(err, data); 40 | }); 41 | }; 42 | } else { 43 | api[apiMethod] = (params, callback) => { 44 | api.request(apiMethod, params, callback); 45 | }; 46 | } 47 | }()); 48 | } 49 | }; 50 | api.init(methods); 51 | return api; 52 | }; 53 | 54 | wcl.DataSource = function(methods) { 55 | // just abstract, see implementation below 56 | // should be implemented methods: 57 | // read(query, callback) return one record as object, callback(err, obj) 58 | // insert(obj, callback) insert one record, callback(err) on done 59 | // update(obj, callback) update one record, callback(err) on done 60 | // delete(query, callback) delete multiple records, callback(err) on done 61 | // may be implemented methods: 62 | // introspect(params, callback) populates DataSource.methods with introspection metadata returning from server 63 | // metadata(params, callback) populates DataSource.metadata with metadata from server 64 | // find(query, callback) return multiple records as Array, callback(err, Array) 65 | }; 66 | 67 | wcl.AjaxDataSource = function(methods) { 68 | const ds = wcl.AjaxAPI(methods); 69 | ds.read = function(query, callback) { 70 | ds.request('read', query, (err, data) => { 71 | // TODO: autocreate Record 72 | // callback(err, wcl.Record({ data:data })); 73 | // 74 | callback(err, data); 75 | }); 76 | }; 77 | return ds; 78 | }; 79 | 80 | wcl.MemoryDataSource = function(params) { // { data:Hash, metadata:Hash } 81 | const ds = {}; 82 | ds.data = params.data; 83 | ds.metadata = params.metadata; 84 | ds.each = function(params, callback) { 85 | for (let i = 0; i < ds.data.length; i++) { 86 | let d = ds.data[i], match = true; 87 | for (const key in params) match = match && (d[key] === params[key]); 88 | if (match) { if (callback(i)) return; } 89 | } 90 | }; 91 | ds.read = function(params, callback) { 92 | const data = ds.data; 93 | ds.each(params, key => { callback(null, data[key]); return true; }); 94 | callback(new Error('Record not found'), null); 95 | }; 96 | ds.insert = function(params, callback) { 97 | ds.data.push(params); 98 | callback(); 99 | }; 100 | ds.update = function(params, callback) { 101 | const data = ds.data; 102 | ds.each(params, key => { data[key] = params; return true; }); 103 | callback(); 104 | }; 105 | ds.delete = function(params, callback) { 106 | const data = ds.data; 107 | ds.each(params, key => { delete data[key]; }); 108 | callback(); 109 | }; 110 | ds.find = function(params, callback) { 111 | let data = ds.data, result = []; 112 | ds.each(params, key => { result.push(data[key]); }); 113 | callback(null, result); 114 | }; 115 | return ds; 116 | }; 117 | 118 | wcl.parse = function(json) { 119 | let result; 120 | eval('result = new Object(' + json + ')'); 121 | return result; 122 | }; 123 | 124 | wcl.htmlEscape = function(content) { 125 | return content.replace(/[&<>"'\/]/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' }[char])); 126 | }; 127 | 128 | wcl.template = function(tpl, data, escapeHtml) { 129 | return tpl.replace(/@([\-\.0-9a-zA-Z]+)@/g, (s, key) => (escapeHtml ? wcl.htmlEscape(data[key]) : data[key])); 130 | }; 131 | 132 | wcl.templateHtml = function(tpl, data) { 133 | return wcl.template(tpl, data, true); 134 | }; 135 | 136 | wcl.request = function(method, url, params, parseResponse, callback) { 137 | let req = new XMLHttpRequest(), data = [], value = ''; 138 | req.open(method, url, true); 139 | for (const key in params) { 140 | if (!params.hasOwnProperty(key)) continue; 141 | value = params[key]; 142 | if (typeof(value) !== 'string') value = JSON.stringify(value); 143 | data.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); 144 | } 145 | data = data.join('&'); 146 | req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 147 | req.setRequestHeader('Content-length', data.length); 148 | req.setRequestHeader('Connection', 'close'); 149 | req.onreadystatechange = function() { 150 | if (req.readyState === 4) { 151 | let err = null, res = req.responseText; 152 | if (req.status === 0 || req.status === 200) { 153 | if (parseResponse) { 154 | try { res = JSON.parse(res); } catch (e) { err = new Error('JSON parse code: ' + e); } 155 | } 156 | } else err = new Error('HTTP error code: ' + req.status); 157 | callback(err, res); 158 | } 159 | }; 160 | try { req.send(data); } catch (e) { } 161 | }; 162 | 163 | wcl.get = function(url, params, callback) { 164 | wcl.request('GET', url, params, true, callback); 165 | }; 166 | 167 | wcl.post = function(url, params, callback) { 168 | wcl.request('POST', url, params, true, callback); 169 | }; 170 | 171 | }(global.wcl = global.wcl || {})); 172 | -------------------------------------------------------------------------------- /JavaScript/4-class-factory/class-inheritance-metaprogramming.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Base class 4 | 5 | class Ground 6 | constructor(area) { 7 | this.area = area; 8 | } 9 | 10 | calculateCost(price) { 11 | return this.area * price; 12 | } 13 | } 14 | 15 | // MetaFactory to build descendant classes 16 | 17 | const inheritGround = mixin => { 18 | 19 | const DescendantGround = function(area) { 20 | this.constructor(area); 21 | this.isEmpty = parseInt(area) <= 0; 22 | }; 23 | 24 | DescendantGround.prototype = Object.create(Ground.prototype); 25 | 26 | // Mixin properties to descendant class prototype 27 | Object.assign(DescendantGround.prototype, mixin); 28 | return DescendantGround; 29 | 30 | }; 31 | 32 | // Create descendant class dynamically 33 | 34 | const LandOwnership = inheritGround({ 35 | category: 'land', 36 | type: 'ownership', 37 | // Add method to descendant class prototype 38 | toString() { 39 | const { category, type, area } = this; 40 | return `${category} ${type} / ${area}`; 41 | } 42 | }); 43 | 44 | // Create and use instance 45 | 46 | const land = new LandOwnership(50); 47 | console.dir(land); 48 | const cost = land.calculateCost(7); 49 | console.log(`Cost is: ${cost} for ${land.toString()}`); 50 | -------------------------------------------------------------------------------- /JavaScript/4-class-factory/class-inheritance-simple.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Base class 4 | 5 | class Ground { 6 | constructor(area) { 7 | this.area = area; 8 | } 9 | 10 | calculateCost(price) { 11 | return this.area * price; 12 | } 13 | } 14 | 15 | // Create descendant class 16 | 17 | function LandOwnership(area) { 18 | this.constructor(area); 19 | this.isEmpty = parseInt(area) <= 0; 20 | } 21 | 22 | // Use protorype inheritance from Ground 23 | LandOwnership.prototype = Object.create(Ground.prototype); 24 | 25 | // Add properties to descendant class prototype 26 | LandOwnership.prototype.category = 'land'; 27 | LandOwnership.prototype.type = 'ownership'; 28 | 29 | // Add method to descendant class prototype 30 | LandOwnership.prototype.toString = function() { 31 | const { category, type, area } = this; 32 | return `${category} ${type} / ${area}`; 33 | }; 34 | 35 | // Create and use instance 36 | 37 | const land = new LandOwnership(50); 38 | console.dir(land); 39 | const cost = land.calculateCost(7); 40 | console.log(`Cost is: ${cost} for ${land.toString()}`); 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-2022 How.Programming.Works contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Metaprogramming with JavaScript examples 2 | 3 | [![Метапрограммирование с примерами на JavaScript](https://img.youtube.com/vi/Ed9onRv4G5Y/0.jpg)](https://www.youtube.com/watch?v=Ed9onRv4G5Y) 4 | --------------------------------------------------------------------------------