1
|
|
|
<?php namespace Tarsana\Functional; |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* This script parses the source files using [dox](https://github.com/tj/dox) |
4
|
|
|
* and generates the unit tests and documentation files. |
5
|
|
|
*/ |
6
|
|
|
require __DIR__ . '/vendor/autoload.php'; |
7
|
|
|
|
8
|
|
|
/** |
9
|
|
|
* Custom Types: |
10
|
|
|
* DoxBlock :: { |
11
|
|
|
* tags: [{ |
12
|
|
|
* type: String, |
13
|
|
|
* string: String, |
14
|
|
|
* types: [String], |
15
|
|
|
* name: String, |
16
|
|
|
* description: String |
17
|
|
|
* ... |
18
|
|
|
* }], |
19
|
|
|
* description: { |
20
|
|
|
* full: String, |
21
|
|
|
* summary: String, |
22
|
|
|
* body: String |
23
|
|
|
* }, |
24
|
|
|
* code: String, |
25
|
|
|
* ctx: { |
26
|
|
|
* type: String, |
27
|
|
|
* name: String, |
28
|
|
|
* ... |
29
|
|
|
* } |
30
|
|
|
* isPrivate: |
31
|
|
|
* isEvent: |
32
|
|
|
* isConstructor: |
33
|
|
|
* line: |
34
|
|
|
* ignore: |
35
|
|
|
* } |
36
|
|
|
* |
37
|
|
|
* Block :: { |
38
|
|
|
* type: file|function|class|method |
39
|
|
|
* name: String // DoxBlock.ctx.name |
40
|
|
|
* params: [{type: String, name: String}] |
41
|
|
|
* return: String |
42
|
|
|
* signatures: [String] |
43
|
|
|
* description: String |
44
|
|
|
* summary: String |
45
|
|
|
* internal: Boolean |
46
|
|
|
* ignore: Boolean |
47
|
|
|
* code: String |
48
|
|
|
* } |
49
|
|
|
* |
50
|
|
|
* Operation :: { |
51
|
|
|
* name: String, |
52
|
|
|
* signature: String |
53
|
|
|
* } |
54
|
|
|
* |
55
|
|
|
* Module :: { |
56
|
|
|
* path: String |
57
|
|
|
* name: String |
58
|
|
|
* docsPath: String |
59
|
|
|
* testsPath: String |
60
|
|
|
* blocks: [Block] |
61
|
|
|
* docs: String |
62
|
|
|
* tests: String |
63
|
|
|
* testsFooter: String |
64
|
|
|
* streamOperations: String |
65
|
|
|
* streamMethods: String |
66
|
|
|
* } |
67
|
|
|
*/ |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* The entry point. |
71
|
|
|
* |
72
|
|
|
* @signature [String] -> IO |
73
|
|
|
* @param array $modules |
74
|
|
|
* @return void |
75
|
|
|
*/ |
76
|
|
|
function build_main($modules) { |
77
|
|
|
build_init_stream_operations(); |
78
|
|
|
each(_f('build_module'), $modules); |
|
|
|
|
79
|
|
|
build_close_stream_operations(); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Writes the header of the stream operations file. |
84
|
|
|
* |
85
|
|
|
* @signature IO |
86
|
|
|
* @return void |
87
|
|
|
*/ |
88
|
|
|
function build_init_stream_operations() { |
89
|
|
|
file_put_contents( |
90
|
|
|
'src/Internal/_stream_operations.php', |
91
|
|
|
"<?php\n\nuse Tarsana\Functional as F;\n\nreturn F\map(F\apply(F\_f('_stream_operation')), [\n\t['then', 'Function -> Any -> Any', F\_f('_stream_then')],\n" |
92
|
|
|
); |
93
|
|
|
file_put_contents( |
94
|
|
|
'docs/stream-operations.md', |
95
|
|
|
"# Stream Operations" |
96
|
|
|
); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
/** |
100
|
|
|
* Writes the footer of the stream operations file. |
101
|
|
|
* |
102
|
|
|
* @signature IO |
103
|
|
|
* @return void |
104
|
|
|
*/ |
105
|
|
|
function build_close_stream_operations() { |
106
|
|
|
file_put_contents( |
107
|
|
|
'src/Internal/_stream_operations.php', |
108
|
|
|
"\n]);\n", FILE_APPEND |
109
|
|
|
); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Extracts the modules files from composer.json. |
114
|
|
|
* |
115
|
|
|
* @signature [String] |
116
|
|
|
* @return array |
117
|
|
|
*/ |
118
|
|
|
function get_modules() { |
119
|
|
|
$composer = json_decode(file_get_contents(__DIR__.'/composer.json')); |
120
|
|
|
return $composer->autoload->files; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Generates unit tests and documentation for a module. |
125
|
|
|
* |
126
|
|
|
* @signature String -> IO |
127
|
|
|
* @param string $path |
128
|
|
|
* @return void |
129
|
|
|
*/ |
130
|
|
|
function build_module($path) { |
131
|
|
|
apply(process_of([ |
132
|
|
|
'module_of', |
133
|
|
|
'generate_docs', |
134
|
|
|
'generate_tests', |
135
|
|
|
'generate_stream_operations', |
136
|
|
|
'generate_stream_methods', |
137
|
|
|
'write_module' |
138
|
|
|
]), [$path]); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Writes the module's docs and tests. |
143
|
|
|
* |
144
|
|
|
* @signature Module -> IO |
145
|
|
|
* @param object $module |
146
|
|
|
* @return void |
147
|
|
|
*/ |
148
|
|
|
function write_module($module) { |
149
|
|
View Code Duplication |
if ($module->docs) { |
|
|
|
|
150
|
|
|
$docsDir = dirname($module->docsPath); |
151
|
|
|
if (!is_dir($docsDir)) |
152
|
|
|
mkdir($docsDir, 0777, true); |
153
|
|
|
file_put_contents($module->docsPath, $module->docs); |
154
|
|
|
} |
155
|
|
View Code Duplication |
if ($module->tests) { |
|
|
|
|
156
|
|
|
$testsDir = dirname($module->testsPath); |
157
|
|
|
if (!is_dir($testsDir)) |
158
|
|
|
mkdir($testsDir, 0777, true); |
159
|
|
|
file_put_contents($module->testsPath, $module->tests); |
160
|
|
|
} |
161
|
|
|
if ($module->streamOperations) { |
162
|
|
|
file_put_contents('src/Internal/_stream_operations.php', $module->streamOperations, FILE_APPEND); |
163
|
|
|
} |
164
|
|
|
if ($module->streamMethods) { |
165
|
|
|
file_put_contents('docs/stream-operations.md', $module->streamMethods, FILE_APPEND); |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Creates a module from a path. |
171
|
|
|
* |
172
|
|
|
* @signature String -> Module |
173
|
|
|
* @param string $path |
174
|
|
|
* @return object |
175
|
|
|
*/ |
176
|
|
|
function module_of($path) { |
177
|
|
|
return apply(process_of([ |
178
|
|
|
'fill_name', |
179
|
|
|
'fill_docs_path', |
180
|
|
|
'fill_tests_path', |
181
|
|
|
'fill_blocks' |
182
|
|
|
]), [(object)['path' => $path]]); |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Fills the name of the Module based on the path. |
187
|
|
|
* 'src/xxx/aaa.php' -> 'aaa' |
188
|
|
|
* |
189
|
|
|
* @signature Module -> Module |
190
|
|
|
* @param object $module |
191
|
|
|
* @return object |
192
|
|
|
*/ |
193
|
|
|
function fill_name($module) { |
194
|
|
|
$module->name = apply(pipe(split('/'), last(), split('.'), head()), [$module->path]); |
195
|
|
|
return $module; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Fills documentation file path based on source file path. |
200
|
|
|
* 'src/xxx.php' -> 'docs/xxx.md' |
201
|
|
|
* |
202
|
|
|
* @signature Module -> Module |
203
|
|
|
* @param object $module |
204
|
|
|
* @return object |
205
|
|
|
*/ |
206
|
|
|
function fill_docs_path($module) { |
207
|
|
|
$module->docsPath = replace(['src', '.php'], ['docs', '.md'], $module->path); |
208
|
|
|
return $module; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Fills tests file path based on source file path. |
213
|
|
|
* 'src/xxx.php' -> 'tests/xxxTest.php' |
214
|
|
|
* |
215
|
|
|
* @signature Module -> Module |
216
|
|
|
* @param object $module |
217
|
|
|
* @return object |
218
|
|
|
*/ |
219
|
|
|
function fill_tests_path($module) { |
220
|
|
|
$name = ucfirst(camelCase($module->name)); |
221
|
|
|
$dir = 'tests' . remove(3, dirname($module->path)); |
222
|
|
|
$module->testsPath = "{$dir}/{$name}Test.php"; |
223
|
|
|
return $module; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Fills the blocks of the Module based on the path. |
228
|
|
|
* |
229
|
|
|
* @signature Module -> Module |
230
|
|
|
* @param array $module |
231
|
|
|
* @return array |
232
|
|
|
*/ |
233
|
|
|
function fill_blocks($module) { |
234
|
|
|
$module->blocks = apply(pipe( |
235
|
|
|
prepend('dox -r < '), // "dox -r < src/...php" |
236
|
|
|
'shell_exec', // "[{...}, ...]" |
237
|
|
|
'json_decode', // [DoxBlock] |
238
|
|
|
map(_f('make_block')) |
239
|
|
|
// sort() |
240
|
|
|
), [$module->path]); |
241
|
|
|
return $module; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Converts a DoxBlock to a Block. |
246
|
|
|
* |
247
|
|
|
* @signature DoxBlock -> Block |
248
|
|
|
* @param object $doxBlock |
249
|
|
|
* @return object |
250
|
|
|
*/ |
251
|
|
|
function make_block($doxBlock) { |
252
|
|
|
$tags = groupBy(get('name'), tags_of($doxBlock)); |
253
|
|
|
|
254
|
|
|
$type = 'function'; |
255
|
|
|
if (has('file', $tags)) $type = 'file'; |
256
|
|
|
if (has('class', $tags)) $type = 'class'; |
257
|
|
|
if (has('method', $tags)) $type = 'method'; |
258
|
|
|
|
259
|
|
|
$params = map(function($tag){ |
260
|
|
|
$parts = split(' ', get('value', $tag)); |
261
|
|
|
return [ |
262
|
|
|
'type' => $parts[0], |
263
|
|
|
'name' => $parts[1] |
264
|
|
|
]; |
265
|
|
|
}, get('param', $tags) ?: []); |
266
|
|
|
|
267
|
|
|
$return = getPath(['return', 0, 'value'], $tags); |
268
|
|
|
$signatures = get('signature', $tags); |
269
|
|
|
if ($signatures) |
270
|
|
|
$signatures = map(get('value'), $signatures); |
271
|
|
|
return (object) [ |
272
|
|
|
'type' => $type, |
273
|
|
|
'name' => getPath(['ctx', 'name'], $doxBlock), |
274
|
|
|
'params' => $params, |
275
|
|
|
'return' => $return, |
276
|
|
|
'signatures' => $signatures, |
277
|
|
|
'description' => getPath(['description', 'full'], $doxBlock), |
278
|
|
|
'summary' => getPath(['description', 'summary'], $doxBlock), |
279
|
|
|
'internal' => has('internal', $tags), |
280
|
|
|
'ignore' => has('ignore', $tags), |
281
|
|
|
'stream' => has('stream', $tags) |
282
|
|
|
// 'code' => get('code', $doxBlock) |
|
|
|
|
283
|
|
|
]; |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Returns an array of tags, each having a name and a value. |
288
|
|
|
* |
289
|
|
|
* @signature DoxBlock -> [{name: String, value: String}] |
290
|
|
|
* @param object $doxBlock |
291
|
|
|
* @return array |
292
|
|
|
*/ |
293
|
|
|
function tags_of($doxBlock) { |
294
|
|
|
if ($doxBlock->tags) |
295
|
|
|
return map(function($tag){ |
296
|
|
|
return (object) [ |
297
|
|
|
'name' => $tag->type, |
298
|
|
|
'value' => $tag->string |
299
|
|
|
]; |
300
|
|
|
}, $doxBlock->tags); |
301
|
|
|
return []; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Generates documentation contents for a module. |
306
|
|
|
* |
307
|
|
|
* @signature Module -> Module |
308
|
|
|
* @param object $module |
309
|
|
|
* @return object |
310
|
|
|
*/ |
311
|
|
|
function generate_docs($module) { |
312
|
|
|
$module->docs = ''; |
313
|
|
|
if (startsWith('_', $module->name)) |
|
|
|
|
314
|
|
|
return $module; |
315
|
|
|
return apply(process_of([ |
316
|
|
|
'generate_docs_header', |
317
|
|
|
'generate_docs_sommaire', |
318
|
|
|
'generate_docs_contents' |
319
|
|
|
]), [$module]); |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Generates documentation header. |
324
|
|
|
* |
325
|
|
|
* @signature Module -> Module |
326
|
|
|
* @param object $module |
327
|
|
|
* @return object |
328
|
|
|
*/ |
329
|
|
|
function generate_docs_header($module) { |
330
|
|
|
$name = $module->name; |
331
|
|
|
$description = get('description', head($module->blocks)); |
332
|
|
|
$module->docs .= "# {$name}\n\n{$description}\n\n"; |
333
|
|
|
return $module; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Generates documentation table of contents. |
338
|
|
|
* |
339
|
|
|
* @signature Module -> Module |
340
|
|
|
* @param object $module |
341
|
|
|
* @return object |
342
|
|
|
*/ |
343
|
|
View Code Duplication |
function generate_docs_sommaire($module) { |
|
|
|
|
344
|
|
|
$blocks = filter ( |
345
|
|
|
satisfiesAll(['ignore' => not(), 'internal' => not(), 'type' => equals('function')]), |
|
|
|
|
346
|
|
|
$module->blocks |
347
|
|
|
); |
348
|
|
|
$items = map(_f('generate_docs_sommaire_item'), $blocks); |
349
|
|
|
$module->docs .= join('', $items); |
350
|
|
|
return $module; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Generates an item of the documentation's table of contents. |
355
|
|
|
* |
356
|
|
|
* @signature Block -> String |
357
|
|
|
* @param object $block |
358
|
|
|
* @return string |
359
|
|
|
*/ |
360
|
|
|
function generate_docs_sommaire_item($block) { |
361
|
|
|
$title = get('name', $block); |
362
|
|
|
$link = lowerCase($title); |
363
|
|
|
return "- [{$title}](#{$link}) - {$block->summary}\n\n"; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Generates documentation contents. |
368
|
|
|
* |
369
|
|
|
* @signature Module -> Module |
370
|
|
|
* @param object $module |
371
|
|
|
* @return object |
372
|
|
|
*/ |
373
|
|
View Code Duplication |
function generate_docs_contents($module) { |
|
|
|
|
374
|
|
|
$blocks = filter ( |
375
|
|
|
satisfiesAll(['ignore' => not(), 'internal' => not()]), |
|
|
|
|
376
|
|
|
$module->blocks |
377
|
|
|
); |
378
|
|
|
$contents = map(_f('generate_docs_contents_item'), $blocks); |
379
|
|
|
$module->docs .= join('', $contents); |
380
|
|
|
return $module; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* Generates an item of the documentation's contents. |
385
|
|
|
* |
386
|
|
|
* @signature Block -> String |
387
|
|
|
* @param object $block |
388
|
|
|
* @return string |
389
|
|
|
*/ |
390
|
|
|
function generate_docs_contents_item($block) { |
391
|
|
|
if ($block->type != 'function') |
392
|
|
|
return ''; |
393
|
|
|
$params = join(', ', map(pipe(values(), join(' ')), get('params', $block))); |
394
|
|
|
$return = get('return', $block); |
395
|
|
|
$prototype = "```php\n{$block->name}({$params}) : {$return}\n```\n\n"; |
396
|
|
|
$signature = ''; |
397
|
|
|
$blockSignature = join("\n", $block->signatures); |
398
|
|
|
if ($blockSignature) |
399
|
|
|
$signature = "```\n{$blockSignature}\n```\n\n"; |
400
|
|
|
return "# {$block->name}\n\n{$prototype}{$signature}{$block->description}\n\n"; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Generates tests contents for a module. |
405
|
|
|
* |
406
|
|
|
* @signature Module -> Module |
407
|
|
|
* @param object $module |
408
|
|
|
* @return object |
409
|
|
|
*/ |
410
|
|
|
function generate_tests($module) { |
411
|
|
|
$module->tests = ''; |
412
|
|
|
$module->testsFooter = ''; |
413
|
|
|
return apply(process_of([ |
414
|
|
|
'generate_tests_header', |
415
|
|
|
'generate_tests_contents', |
416
|
|
|
'generate_tests_footer' |
417
|
|
|
]), [$module]); |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
/** |
421
|
|
|
* Generates module's tests header. |
422
|
|
|
* |
423
|
|
|
* @signature Module -> Module |
424
|
|
|
* @param object $module |
425
|
|
|
* @return object |
426
|
|
|
*/ |
427
|
|
|
function generate_tests_header($module) { |
428
|
|
|
$namespace = "Tarsana\UnitTests\Functional"; |
429
|
|
|
$additionalNamespace = replace("/", "\\", remove(6, dirname($module->testsPath))); |
430
|
|
|
if ($additionalNamespace) |
431
|
|
|
$namespace .= "\\" . $additionalNamespace; |
432
|
|
|
$name = remove(-4, last(split("/", $module->testsPath))); |
433
|
|
|
$module->tests .= "<?php namespace {$namespace};\n\nuse Tarsana\Functional as F;\n\nclass {$name} extends \Tarsana\UnitTests\Functional\UnitTest {\n"; |
434
|
|
|
return $module; |
435
|
|
|
} |
436
|
|
|
|
437
|
|
|
/** |
438
|
|
|
* Generates module's tests contents. |
439
|
|
|
* |
440
|
|
|
* @signature Module -> Module |
441
|
|
|
* @param object $module |
442
|
|
|
* @return object |
443
|
|
|
*/ |
444
|
|
|
function generate_tests_contents($module) { |
445
|
|
|
$blocks = filter ( |
446
|
|
|
satisfiesAll(['ignore' => not()]), |
|
|
|
|
447
|
|
|
$module->blocks |
448
|
|
|
); |
449
|
|
|
$contents = join("\n", map(function($block) use($module) { |
450
|
|
|
return generate_tests_contents_item($block, $module); |
451
|
|
|
}, $blocks)); |
452
|
|
|
if (trim($contents) != '') |
453
|
|
|
$module->tests .= $contents; |
454
|
|
|
else |
455
|
|
|
$module->tests = ''; |
456
|
|
|
return $module; |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Generates a test for a module. |
461
|
|
|
* |
462
|
|
|
* @signature Block -> Module -> String |
463
|
|
|
* @param object $block |
464
|
|
|
* @param object $module |
465
|
|
|
* @return string |
466
|
|
|
*/ |
467
|
|
|
function generate_tests_contents_item($block, $module) { |
468
|
|
|
if ($block->type != 'function') |
469
|
|
|
return ''; |
470
|
|
|
|
471
|
|
|
$code = apply(pipe( |
472
|
|
|
_f('code_from_description'), |
473
|
|
|
chunks("\"\"''{}[]()", "\n"), |
474
|
|
|
map(function($part) use($module) { |
475
|
|
|
return add_assertions($part, $module); |
476
|
|
|
}), |
477
|
|
|
filter(pipe('trim', notEq(''))), |
478
|
|
|
chain(split("\n")), |
479
|
|
|
map(prepend("\t\t")), |
480
|
|
|
join("\n") |
481
|
|
|
), [$block]); |
482
|
|
|
|
483
|
|
|
if ('' == trim($code)) |
484
|
|
|
return ''; |
485
|
|
|
return prepend("\tpublic function test_{$block->name}() {\n", |
486
|
|
|
append("\n\t}\n", $code) |
487
|
|
|
); |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
/** |
491
|
|
|
* Extracts the code snippet from the description of a block. |
492
|
|
|
* |
493
|
|
|
* @signature Block -> String |
494
|
|
|
* @param object $block |
495
|
|
|
* @return string |
496
|
|
|
*/ |
497
|
|
|
function code_from_description($block) { |
498
|
|
|
$description = get('description', $block); |
499
|
|
|
if (!contains('```php', $description)) |
500
|
|
|
return ''; |
501
|
|
|
$code = remove(7 + indexOf('```php', $description), $description); |
502
|
|
|
return remove(-4, trim($code)); |
503
|
|
|
} |
504
|
|
|
|
505
|
|
|
/** |
506
|
|
|
* Adds assertions to a part of the code. |
507
|
|
|
* |
508
|
|
|
* @signature String -> String |
509
|
|
|
* @param string $part |
510
|
|
|
* @return string |
511
|
|
|
*/ |
512
|
|
|
function add_assertions($part, $module) { |
513
|
|
|
if (contains('; //=> ', $part)) { |
514
|
|
|
$pieces = split('; //=> ', $part); |
515
|
|
|
$part = "\$this->assertEquals({$pieces[1]}, {$pieces[0]});"; |
516
|
|
|
} |
517
|
|
|
elseif (contains('; // throws ', $part)) { |
518
|
|
|
$pieces = split('; // throws ', $part); |
519
|
|
|
$variables = match('/ \$[0-9a-zA-Z_]+/', $pieces[0]); |
520
|
|
|
$use = ''; |
521
|
|
|
if (length($variables)) { |
522
|
|
|
$variables = join(', ', map('trim', $variables)); |
523
|
|
|
$use = "use({$variables}) "; |
524
|
|
|
} |
525
|
|
|
return "\$this->assertErrorThrown(function() {$use}{\n\t$pieces[0]; \n},\n{$pieces[1]});"; |
526
|
|
|
} |
527
|
|
|
elseif (startsWith('class ', $part) || startsWith('function ', $part)) { |
|
|
|
|
528
|
|
|
$module->testsFooter .= $part . "\n\n"; |
529
|
|
|
$part = ''; |
530
|
|
|
} |
531
|
|
|
return $part; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
/** |
535
|
|
|
* Generates module's tests footer. |
536
|
|
|
* |
537
|
|
|
* @signature Module -> Module |
538
|
|
|
* @param object $module |
539
|
|
|
* @return object |
540
|
|
|
*/ |
541
|
|
|
function generate_tests_footer($module) { |
542
|
|
|
if ($module->tests) |
543
|
|
|
$module->tests .= "}\n\n{$module->testsFooter}"; |
544
|
|
|
return $module; |
545
|
|
|
} |
546
|
|
|
|
547
|
|
|
/** |
548
|
|
|
* Generates module's stream operations. |
549
|
|
|
* |
550
|
|
|
* @signature Module -> Module |
551
|
|
|
* @param array $module |
552
|
|
|
* @return array |
553
|
|
|
*/ |
554
|
|
|
function generate_stream_operations($module) { |
555
|
|
|
$blocks = filter ( |
556
|
|
|
satisfiesAll(['ignore' => equals(false), 'stream' => equals(true)]), |
557
|
|
|
$module->blocks |
558
|
|
|
); |
559
|
|
|
$operations = map(_f('stream_operation_declaration'), chain(_f('stream_operations_of_block'), $blocks)); |
560
|
|
|
$module->streamOperations = join("", $operations); |
561
|
|
|
return $module; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
/** |
565
|
|
|
* Gets stream operations from a block. |
566
|
|
|
* |
567
|
|
|
* @signature Block -> [Operation] |
568
|
|
|
* @param object $block |
569
|
|
|
* @return string |
570
|
|
|
*/ |
571
|
|
|
function stream_operations_of_block($block) { |
572
|
|
|
return map(function($signature) use($block) { |
573
|
|
|
return (object) [ |
574
|
|
|
'name' => $block->name, |
575
|
|
|
'signature' => normalize_signature($signature) |
576
|
|
|
]; |
577
|
|
|
}, get('signatures', $block)); |
578
|
|
|
} |
579
|
|
|
|
580
|
|
|
/** |
581
|
|
|
* Converts a formal signature to a stream signature. |
582
|
|
|
* [a] becomes List |
583
|
|
|
* {k: v} becomes Array|Object |
584
|
|
|
* (a -> b) becomes Function |
585
|
|
|
* * becomes Any |
586
|
|
|
* |
587
|
|
|
* @signature String -> String |
588
|
|
|
* @param string $signature |
589
|
|
|
* @return string |
590
|
|
|
*/ |
591
|
|
|
function normalize_signature($signature) { |
592
|
|
|
// This is not the best way to do it :P |
593
|
|
|
return join(' -> ', map(pipe( |
594
|
|
|
regReplace('/Maybe\([a-z][^\)]*\)/', 'Any'), |
595
|
|
|
regReplace('/Maybe\(([^\)]+)\)/', '$1|Null'), |
596
|
|
|
regReplace('/\([^\)]+\)/', 'Function'), |
597
|
|
|
regReplace('/\[[^\]]+\]/', 'List'), |
598
|
|
|
regReplace('/\{[^\}]+\}/', 'Object|Array'), |
599
|
|
|
regReplace('/^.$/', 'Any'), |
600
|
|
|
regReplace('/[\(\)\[\]\{\}]/', '') |
601
|
|
|
), chunks('(){}', ' -> ', $signature))); |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
/** |
605
|
|
|
* Converts a stream operation to declaration array. |
606
|
|
|
* |
607
|
|
|
* @signature Operation -> String |
608
|
|
|
* @param object $operation |
609
|
|
|
* @return string |
610
|
|
|
*/ |
611
|
|
|
function stream_operation_declaration($operation) { |
612
|
|
|
$name = rtrim($operation->name, '_'); |
613
|
|
|
return "\t['{$name}', '{$operation->signature}', F\\{$operation->name}()],\n"; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* Generates module's stream methods documentation. |
618
|
|
|
* |
619
|
|
|
* @signature Module -> Module |
620
|
|
|
* @param array $module |
621
|
|
|
* @return array |
622
|
|
|
*/ |
623
|
|
|
function generate_stream_methods($module) { |
624
|
|
|
$blocks = filter ( |
625
|
|
|
satisfiesAll(['ignore' => equals(false), 'stream' => equals(true)]), |
626
|
|
|
$module->blocks |
627
|
|
|
); |
628
|
|
|
$methods = map(stream_method_link($module->name), $blocks); |
629
|
|
|
$module->streamMethods = (length($methods) > 0) |
630
|
|
|
? "\n\n## {$module->name}\n\n" . join("\n", $methods) |
631
|
|
|
: ''; |
632
|
|
|
return $module; |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
/** |
636
|
|
|
* Gets an element of the stream methods list. |
637
|
|
|
* |
638
|
|
|
* @signature String -> Block -> String |
639
|
|
|
* @param string $moduleName |
|
|
|
|
640
|
|
|
* @param object $block |
|
|
|
|
641
|
|
|
* @return string |
642
|
|
|
*/ |
643
|
|
|
function stream_method_link() { |
644
|
|
|
static $curried = false; |
645
|
|
|
$curried = $curried ?: curry(function($moduleName, $block) { |
646
|
|
|
return "- [{$block->name}](https://github.com/tarsana/functional/blob/master/docs/{$moduleName}.md#{$block->name}) - {$block->summary}\n"; |
647
|
|
|
}); |
648
|
|
|
return _apply($curried, func_get_args()); |
649
|
|
|
} |
650
|
|
|
|
651
|
|
|
/** |
652
|
|
|
* process_of(['f1', 'f2']) == pipe(_f('f1'), _f('f2')); |
653
|
|
|
* |
654
|
|
|
* @signature [String] -> Function |
655
|
|
|
* @param array $fns |
656
|
|
|
* @return callable |
657
|
|
|
*/ |
658
|
|
|
function process_of($fns) { |
659
|
|
|
return apply(_f('pipe'), map(_f('_f'), $fns)); |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
/** |
663
|
|
|
* Dump a variable and returns it. |
664
|
|
|
* |
665
|
|
|
* @signature a -> a |
666
|
|
|
* @param mixed $something |
|
|
|
|
667
|
|
|
* @return mixed |
668
|
|
|
*/ |
669
|
|
|
function log() { |
670
|
|
|
$log = function($something) { |
671
|
|
|
echo toString($something); |
672
|
|
|
return $something; |
673
|
|
|
}; |
674
|
|
|
return apply(curry($log), func_get_args()); |
675
|
|
|
} |
676
|
|
|
|
677
|
|
|
// Convert Warnings to Exceptions |
678
|
|
|
set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) { |
|
|
|
|
679
|
|
|
if (0 === error_reporting()) |
680
|
|
|
return false; |
681
|
|
|
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); |
682
|
|
|
}); |
683
|
|
|
|
684
|
|
|
// Run the build |
685
|
|
|
build_main(get_modules()); |
686
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.