binary-collections 2.0.13 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/binaries/binary-executor.cjs +307 -238
  2. package/binaries/clean-nodemodule.cjs +307 -238
  3. package/binaries/clean-nodemodules.cjs +307 -238
  4. package/binaries/composer.cjs +323 -0
  5. package/binaries/composer.cmd +2 -0
  6. package/binaries/composer.phar +0 -0
  7. package/binaries/dev.cjs +307 -238
  8. package/binaries/empty.cjs +307 -238
  9. package/binaries/git-reduce-size.cjs +307 -238
  10. package/binaries/javakill.cjs +307 -238
  11. package/binaries/kill-process.cjs +307 -238
  12. package/binaries/nodekill.cjs +307 -238
  13. package/binaries/prod.cjs +307 -238
  14. package/binaries/py.cjs +307 -238
  15. package/binaries/rmfind.cjs +307 -238
  16. package/binaries/rmx.cjs +307 -238
  17. package/binaries/submodule-token.cjs +307 -238
  18. package/binaries/test-cjs.cjs +307 -238
  19. package/binaries/test-esm.cjs +307 -238
  20. package/binaries/yarn-clean.cjs +307 -238
  21. package/binaries/yc.cjs +307 -238
  22. package/binaries/ycw.cjs +307 -238
  23. package/docs-src/binary-collections.md +1 -1
  24. package/docs-src/clean-github-actions-caches.md +1 -1
  25. package/docs-src/copy-move-file.md +1 -4
  26. package/docs-src/del-ps.md +1 -1
  27. package/docs-src/find-node-modules.md +1 -1
  28. package/docs-src/generate-test-ci.md +56 -0
  29. package/docs-src/get-latest-workflow-status.md +100 -0
  30. package/docs-src/git-diff.md +1 -1
  31. package/docs-src/git-fix.md +1 -1
  32. package/docs-src/git-purge.md +1 -1
  33. package/docs-src/kill-night-crows.md +1 -1
  34. package/docs-src/node-cache-cleaner.md +2 -2
  35. package/docs-src/node-package-packer.md +1 -1
  36. package/docs-src/opencode-cli.md +127 -0
  37. package/docs-src/package-resolutions-updater.md +1 -1
  38. package/docs-src/rmpath.md +1 -1
  39. package/docs-src/run-by-checksum.md +1 -1
  40. package/docs-src/submodule-remove.md +1 -1
  41. package/docs-src/upload-backend.md +29 -0
  42. package/docs-src/vscode-cli.md +84 -0
  43. package/docs-src/workflow-badge.md +120 -0
  44. package/lib/binary-collections/config.cjs +14 -2
  45. package/lib/binary-collections/config.d.cts +10 -0
  46. package/lib/binary-collections/config.mjs +2 -2
  47. package/lib/binary-collections/findScript.cjs +43 -21
  48. package/lib/binary-collections/findScript.mjs +2 -2
  49. package/lib/binary-collections/listScript.cjs +43 -21
  50. package/lib/binary-collections/listScript.mjs +2 -2
  51. package/lib/binary-collections.cjs +43 -21
  52. package/lib/binary-collections.mjs +6 -6
  53. package/lib/chunk-2SJKVOTN.mjs +146 -0
  54. package/lib/{chunk-2MN4VPV2.mjs → chunk-3F6EIHYG.mjs} +2 -2
  55. package/lib/chunk-546KAIYT.mjs +113 -0
  56. package/lib/chunk-56BVU63B.mjs +86 -0
  57. package/lib/chunk-5WAOOOGZ.mjs +77 -0
  58. package/lib/chunk-72XTQ3CK.mjs +45 -0
  59. package/lib/chunk-7N52Z4IJ.mjs +39 -0
  60. package/lib/chunk-7Q6YEUQF.mjs +246 -0
  61. package/lib/{chunk-NQXUYO67.mjs → chunk-AJ3OIYYP.mjs} +43 -21
  62. package/lib/chunk-AQZ7LMFS.mjs +100 -0
  63. package/lib/chunk-BDCMTOZI.mjs +246 -0
  64. package/lib/chunk-BEUM4LH4.mjs +184 -0
  65. package/lib/{chunk-TBWXE7ST.mjs → chunk-BO4TZS4Q.mjs} +5 -2
  66. package/lib/chunk-CM3IC5YC.mjs +226 -0
  67. package/lib/{chunk-FLYSZFLW.mjs → chunk-D42YBRZW.mjs} +1 -1
  68. package/lib/chunk-FR3DMHJC.mjs +146 -0
  69. package/lib/chunk-I3O5ZRYU.mjs +77 -0
  70. package/lib/chunk-J4M5EL5P.mjs +108 -0
  71. package/lib/chunk-JK3MG2KF.mjs +236 -0
  72. package/lib/chunk-JMUFQSPE.mjs +184 -0
  73. package/lib/chunk-JVMLKHD2.mjs +62 -0
  74. package/lib/chunk-KAT2JNLZ.mjs +146 -0
  75. package/lib/{chunk-CD3HF3LK.mjs → chunk-KRCPFWIF.mjs} +6 -3
  76. package/lib/chunk-LACQTD5V.mjs +225 -0
  77. package/lib/chunk-MCCMMZSM.mjs +60 -0
  78. package/lib/chunk-OA2RKEY3.mjs +162 -0
  79. package/lib/{chunk-X2B3X7D4.mjs → chunk-PAZH45HS.mjs} +7 -1
  80. package/lib/chunk-QZMGBDSA.mjs +32 -0
  81. package/lib/chunk-RKPIBGKE.mjs +61 -0
  82. package/lib/chunk-SARIXFHP.mjs +44 -0
  83. package/lib/chunk-SJYP66BO.mjs +62 -0
  84. package/lib/chunk-SWUAEY4H.mjs +44 -0
  85. package/lib/chunk-TP3O2JGW.mjs +88 -0
  86. package/lib/chunk-UAIF5VIA.mjs +89 -0
  87. package/lib/chunk-UDZBVKXH.mjs +94 -0
  88. package/lib/chunk-UEOWRYAN.mjs +32 -0
  89. package/lib/chunk-UHPFLJXH.mjs +227 -0
  90. package/lib/chunk-UYNBNLV5.mjs +113 -0
  91. package/lib/chunk-WOC4FZ6F.mjs +164 -0
  92. package/lib/chunk-X7UVQ6ZC.mjs +183 -0
  93. package/lib/{chunk-RDN6HF5Z.mjs → chunk-XI67TI46.mjs} +1 -1
  94. package/lib/chunk-XW5NZAKI.mjs +82 -0
  95. package/lib/chunk-YLV4QATP.mjs +86 -0
  96. package/lib/chunk-YWSLMAQ7.mjs +65 -0
  97. package/lib/chunk-ZB4IQ6VJ.mjs +46 -0
  98. package/lib/cross-env/index.mjs +3 -3
  99. package/lib/del-gradle.cjs +1 -1
  100. package/lib/del-gradle.mjs +22 -16
  101. package/lib/del-node-modules.cjs +1 -1
  102. package/lib/del-node-modules.mjs +148 -142
  103. package/lib/find-node-modules-cli.cjs +1 -1
  104. package/lib/find-node-modules-cli.mjs +10 -4
  105. package/lib/{git-diff-cli.cjs → git/git-diff-cli.cjs} +18 -6
  106. package/lib/{git-diff-cli.mjs → git/git-diff-cli.mjs} +7 -7
  107. package/lib/{git-diff.cjs → git/git-diff.cjs} +16 -4
  108. package/lib/{git-diff.js → git/git-diff.js} +3 -3
  109. package/lib/{git-diff.mjs → git/git-diff.mjs} +6 -6
  110. package/lib/{git-fix.cjs → git/git-fix.cjs} +134 -3
  111. package/lib/{git-fix.mjs → git/git-fix.mjs} +19 -14
  112. package/lib/{git-purge.cjs → git/git-purge.cjs} +1 -1
  113. package/lib/{git-purge.mjs → git/git-purge.mjs} +4 -4
  114. package/lib/git/user-config.cjs +131 -1
  115. package/lib/git/user-config.mjs +3 -1
  116. package/lib/{clean-github-actions-caches-cli.cjs → github-workflows/clean-github-actions-caches-cli.cjs} +38 -10
  117. package/lib/{clean-github-actions-caches-cli.mjs → github-workflows/clean-github-actions-caches-cli.mjs} +7 -6
  118. package/lib/{clean-github-actions-caches.cjs → github-workflows/clean-github-actions-caches.cjs} +38 -10
  119. package/lib/{clean-github-actions-caches.mjs → github-workflows/clean-github-actions-caches.mjs} +5 -4
  120. package/lib/github-workflows/generate-test-ci-step-cli.cjs +240 -0
  121. package/lib/github-workflows/generate-test-ci-step-cli.d.mts +2 -0
  122. package/lib/github-workflows/generate-test-ci-step-cli.mjs +132 -0
  123. package/lib/github-workflows/get-latest-workflow-status-cli.cjs +541 -0
  124. package/lib/github-workflows/get-latest-workflow-status-cli.d.mts +2 -0
  125. package/lib/github-workflows/get-latest-workflow-status-cli.mjs +61 -0
  126. package/lib/github-workflows/get-latest-workflow-status.cjs +56 -0
  127. package/lib/github-workflows/get-latest-workflow-status.d.mts +1 -0
  128. package/lib/github-workflows/get-latest-workflow-status.mjs +8 -0
  129. package/lib/github-workflows/utils.cjs +271 -0
  130. package/lib/github-workflows/utils.d.cts +76 -0
  131. package/lib/github-workflows/utils.mjs +8 -0
  132. package/lib/github-workflows/workflow-badge-cli.cjs +722 -0
  133. package/lib/github-workflows/workflow-badge-cli.d.mts +2 -0
  134. package/lib/github-workflows/workflow-badge-cli.mjs +98 -0
  135. package/lib/github-workflows/workflow-badge-generator.cjs +200 -0
  136. package/lib/github-workflows/workflow-badge-generator.d.mts +14 -0
  137. package/lib/github-workflows/workflow-badge-generator.mjs +8 -0
  138. package/lib/github-workflows/workflow-test-data.cjs +73 -0
  139. package/lib/github-workflows/workflow-test-data.d.cts +63 -0
  140. package/lib/github-workflows/workflow-test-data.mjs +6 -0
  141. package/lib/opencode/cli/auth-rotate.cjs +143 -0
  142. package/lib/opencode/cli/auth-rotate.d.ts +1 -0
  143. package/lib/opencode/cli/auth-rotate.js +70 -0
  144. package/lib/opencode/cli/auth-rotate.mjs +10 -0
  145. package/lib/opencode/cli/list-projects.cjs +184 -0
  146. package/lib/opencode/cli/list-projects.d.ts +1 -0
  147. package/lib/opencode/cli/list-projects.js +32 -0
  148. package/lib/opencode/cli/list-projects.mjs +11 -0
  149. package/lib/opencode/cli/list-sessions.cjs +215 -0
  150. package/lib/opencode/cli/list-sessions.d.ts +1 -0
  151. package/lib/opencode/cli/list-sessions.js +45 -0
  152. package/lib/opencode/cli/list-sessions.mjs +11 -0
  153. package/lib/opencode/database.cjs +349 -0
  154. package/lib/opencode/database.d.ts +91 -0
  155. package/lib/opencode/database.js +252 -0
  156. package/lib/opencode/database.mjs +28 -0
  157. package/lib/opencode/database.runner.cjs +145 -0
  158. package/lib/opencode/database.runner.d.ts +1 -0
  159. package/lib/opencode/database.runner.js +56 -0
  160. package/lib/opencode/database.runner.mjs +37 -0
  161. package/lib/opencode/opencode-zen.runner.cjs +48 -0
  162. package/lib/opencode/opencode-zen.runner.d.mts +1 -0
  163. package/lib/opencode/opencode-zen.runner.mjs +31 -0
  164. package/lib/opencode/sqlite.cjs +114 -0
  165. package/lib/opencode/sqlite.d.ts +18 -0
  166. package/lib/opencode/sqlite.js +82 -0
  167. package/lib/opencode/sqlite.mjs +10 -0
  168. package/lib/opencode/storage.cjs +124 -0
  169. package/lib/opencode/storage.d.ts +27 -0
  170. package/lib/opencode/storage.js +101 -0
  171. package/lib/opencode/storage.mjs +38 -0
  172. package/lib/opencode/storage.runner.cjs +50 -0
  173. package/lib/opencode/storage.runner.d.ts +1 -0
  174. package/lib/opencode/storage.runner.js +13 -0
  175. package/lib/opencode/storage.runner.mjs +29 -0
  176. package/lib/opencode/types.cjs +17 -0
  177. package/lib/opencode/types.d.ts +31 -0
  178. package/lib/opencode/types.js +2 -0
  179. package/lib/opencode/types.mjs +7 -0
  180. package/lib/opencode/utils/check-api.cjs +59 -0
  181. package/lib/opencode/utils/check-api.d.ts +12 -0
  182. package/lib/opencode/utils/check-api.js +46 -0
  183. package/lib/opencode/utils/check-api.mjs +8 -0
  184. package/lib/opencode-cli.cjs +473 -0
  185. package/lib/opencode-cli.d.ts +2 -0
  186. package/lib/opencode-cli.js +115 -0
  187. package/lib/opencode-cli.mjs +111 -0
  188. package/lib/package-resolutions-updater-cli.cjs +181 -154
  189. package/lib/package-resolutions-updater-cli.mjs +3 -2
  190. package/lib/package-resolutions-updater.cjs +181 -154
  191. package/lib/package-resolutions-updater.d.mts +12 -3
  192. package/lib/package-resolutions-updater.mjs +3 -2
  193. package/lib/print-directory-tree.cjs +131 -3
  194. package/lib/print-directory-tree.mjs +6 -3
  195. package/lib/rmpath-cli.cjs +131 -5
  196. package/lib/rmpath-cli.mjs +3 -1
  197. package/lib/rmpath.cjs +147 -11
  198. package/lib/rmpath.mjs +3 -1
  199. package/lib/run-by-checksum/hash.cjs +18 -2
  200. package/lib/run-by-checksum/hash.d.ts +4 -1
  201. package/lib/run-by-checksum/hash.js +38 -4
  202. package/lib/run-by-checksum/hash.mjs +1 -1
  203. package/lib/run-by-checksum/run.cjs +18 -2
  204. package/lib/run-by-checksum/run.mjs +2 -2
  205. package/lib/run-by-checksum-cli.cjs +18 -2
  206. package/lib/run-by-checksum-cli.mjs +2 -2
  207. package/lib/submodule-install.cjs +130 -4
  208. package/lib/submodule-install.mjs +5 -4
  209. package/lib/submodule-remove-cli.cjs +131 -5
  210. package/lib/submodule-remove-cli.mjs +3 -1
  211. package/lib/submodule-remove.cjs +146 -5
  212. package/lib/submodule-remove.mjs +3 -1
  213. package/lib/utils/findEnvFiles.cjs +3 -0
  214. package/lib/utils/findEnvFiles.d.cts +2 -2
  215. package/lib/utils/findEnvFiles.mjs +1 -1
  216. package/lib/vscode/project.cjs +0 -0
  217. package/lib/vscode/project.d.ts +0 -0
  218. package/lib/vscode/project.js +1 -0
  219. package/lib/vscode/project.mjs +7 -0
  220. package/lib/vscode/storage.cjs +138 -0
  221. package/lib/vscode/storage.d.ts +51 -0
  222. package/lib/vscode/storage.js +169 -0
  223. package/lib/vscode/storage.mjs +42 -0
  224. package/lib/vscode/storage.runner.cjs +125 -0
  225. package/lib/vscode/storage.runner.d.ts +1 -0
  226. package/lib/vscode/storage.runner.js +47 -0
  227. package/lib/vscode/storage.runner.mjs +60 -0
  228. package/lib/vscode-cli.cjs +155 -0
  229. package/lib/vscode-cli.d.ts +2 -0
  230. package/lib/vscode-cli.js +80 -0
  231. package/lib/vscode-cli.mjs +71 -0
  232. package/package.json +43 -21
  233. package/readme.md +41 -8
  234. package/releases/readme.md +1 -1
  235. package/src/github-workflows/generate-test-ci-step-cli.mjs +126 -0
  236. package/vendor/clue/ndjson-react/README.md +365 -0
  237. package/vendor/composer/pcre/README.md +189 -0
  238. package/vendor/composer/semver/README.md +99 -0
  239. package/vendor/composer/xdebug-handler/README.md +305 -0
  240. package/vendor/ergebnis/agent-detector/README.md +107 -0
  241. package/vendor/evenement/evenement/README.md +64 -0
  242. package/vendor/fidry/cpu-core-counter/README.md +138 -0
  243. package/vendor/friendsofphp/php-cs-fixer/README.md +97 -0
  244. package/vendor/psr/container/README.md +13 -0
  245. package/vendor/psr/event-dispatcher/README.md +6 -0
  246. package/vendor/psr/log/README.md +58 -0
  247. package/vendor/react/cache/README.md +367 -0
  248. package/vendor/react/child-process/README.md +619 -0
  249. package/vendor/react/dns/README.md +453 -0
  250. package/vendor/react/event-loop/README.md +930 -0
  251. package/vendor/react/promise/README.md +722 -0
  252. package/vendor/react/socket/README.md +1564 -0
  253. package/vendor/react/stream/README.md +1249 -0
  254. package/vendor/sebastian/diff/README.md +151 -0
  255. package/vendor/symfony/console/README.md +30 -0
  256. package/vendor/symfony/deprecation-contracts/README.md +26 -0
  257. package/vendor/symfony/event-dispatcher/README.md +25 -0
  258. package/vendor/symfony/event-dispatcher-contracts/README.md +9 -0
  259. package/vendor/symfony/filesystem/README.md +23 -0
  260. package/vendor/symfony/finder/README.md +24 -0
  261. package/vendor/symfony/options-resolver/README.md +25 -0
  262. package/vendor/symfony/polyfill-ctype/README.md +12 -0
  263. package/vendor/symfony/polyfill-intl-grapheme/README.md +32 -0
  264. package/vendor/symfony/polyfill-intl-normalizer/README.md +14 -0
  265. package/vendor/symfony/polyfill-mbstring/README.md +13 -0
  266. package/vendor/symfony/polyfill-php80/README.md +25 -0
  267. package/vendor/symfony/polyfill-php81/README.md +18 -0
  268. package/vendor/symfony/polyfill-php84/README.md +23 -0
  269. package/vendor/symfony/polyfill-php85/README.md +20 -0
  270. package/vendor/symfony/process/README.md +23 -0
  271. package/vendor/symfony/service-contracts/README.md +9 -0
  272. package/vendor/symfony/stopwatch/README.md +52 -0
  273. package/vendor/symfony/string/README.md +24 -0
  274. package/lib/del-gradle.js +0 -16
  275. package/lib/del-node-modules.js +0 -211
  276. package/lib/find-node-modules-cli.js +0 -4
  277. /package/lib/{clean-github-actions-caches-cli.d.cts → del-gradle.d.cts} +0 -0
  278. /package/lib/{del-gradle.d.ts → del-node-modules.d.cts} +0 -0
  279. /package/lib/{find-node-modules-cli.d.ts → find-node-modules-cli.d.cts} +0 -0
  280. /package/lib/{git-diff-cli.d.ts → git/git-diff-cli.d.ts} +0 -0
  281. /package/lib/{git-diff-cli.js → git/git-diff-cli.js} +0 -0
  282. /package/lib/{git-diff.d.ts → git/git-diff.d.ts} +0 -0
  283. /package/lib/{git-fix.d.cts → git/git-fix.d.cts} +0 -0
  284. /package/lib/{git-purge.d.cts → git/git-purge.d.cts} +0 -0
  285. /package/lib/{del-node-modules.d.ts → github-workflows/clean-github-actions-caches-cli.d.cts} +0 -0
  286. /package/lib/{clean-github-actions-caches.d.cts → github-workflows/clean-github-actions-caches.d.cts} +0 -0
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs-extra';
3
+ import path from 'upath';
4
+ import { fileURLToPath } from 'url';
5
+ import yaml from 'yaml';
6
+ import { execSync } from 'child_process';
7
+ import { glob } from 'glob';
8
+ import minimist from 'minimist';
9
+ import actionObject from './workflow-test-data.cjs';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ let actionFile = path.resolve(process.cwd(), '.github/workflows/test.yml');
15
+
16
+ const DEFAULT_PATTERNS = [
17
+ 'test/**/*.test.{js,cjs,mjs,ts}',
18
+ 'test/**/*.spec.{js,cjs,mjs,ts}',
19
+ 'tests/**/*.test.{js,cjs,mjs,ts}',
20
+ 'tests/**/*.spec.{js,cjs,mjs,ts}'
21
+ ];
22
+
23
+ function showHelp() {
24
+ console.log(`\
25
+ Usage: generate-test-ci-step.mjs [options]
26
+
27
+ Options:
28
+ -p, --pattern <glob> Test file patterns to search for (can be specified multiple times)
29
+ --ignore, --ex <glob> Ignore patterns to exclude from results (can be specified multiple times)
30
+ -o, --output <file> Output YAML file path (default: .github/workflows/test.yml)
31
+ -h, --help Show this help message
32
+
33
+ Examples:
34
+ $ node src/github-workflows/generate-test-ci-step.mjs
35
+ $ node src/github-workflows/generate-test-ci-step.mjs -p "test/**/*.test.js"
36
+ $ node src/github-workflows/generate-test-ci-step.mjs --pattern "test/**/*.test.js" --ignore "**/fixtures/**"
37
+ $ node src/github-workflows/generate-test-ci-step.mjs -o .github/workflows/ci.yml -p "src/**/*.test.ts" --ignore "**/node_modules/**"
38
+ `);
39
+ }
40
+
41
+ async function collectTests(patterns, ignorePatterns) {
42
+ const entries = await glob(patterns, {
43
+ onlyFiles: true,
44
+ cwd: process.cwd(),
45
+ ignore: ignorePatterns
46
+ });
47
+ return entries.map((p) => p.replace(/\\/g, '/')).sort();
48
+ }
49
+
50
+ async function main() {
51
+ const argv = minimist(process.argv.slice(2), {
52
+ string: ['pattern', 'ignore', 'ex', 'output'],
53
+ alias: {
54
+ p: 'pattern',
55
+ o: 'output',
56
+ h: 'help'
57
+ }
58
+ });
59
+
60
+ // Merge --ex into --ignore so both flags work
61
+ if (argv.ex) {
62
+ const exList = Array.isArray(argv.ex) ? argv.ex : [argv.ex];
63
+ argv.ignore = argv.ignore ? [].concat(argv.ignore).concat(exList) : exList;
64
+ }
65
+
66
+ if (argv.help) {
67
+ showHelp();
68
+ process.exit(0);
69
+ }
70
+
71
+ if (argv.output) {
72
+ actionFile = path.resolve(process.cwd(), argv.output);
73
+ }
74
+
75
+ const patterns = argv.pattern ? (Array.isArray(argv.pattern) ? argv.pattern : [argv.pattern]) : DEFAULT_PATTERNS;
76
+
77
+ const ignorePatterns = argv.ignore ? (Array.isArray(argv.ignore) ? argv.ignore : [argv.ignore]) : [];
78
+
79
+ const files = await collectTests(patterns, ignorePatterns);
80
+ for (const file of files) {
81
+ const ext = (path.extname(file) || '').toLowerCase();
82
+ const filename = path.basename(file);
83
+ let runCmd;
84
+ if (ext === '.mjs') {
85
+ runCmd = `if [ -f "${file}" ]; then\n bash bin/test-esm --testPathPatterns="${filename}"\nelse\n echo "Skipping missing test file: ${file}"\nfi`;
86
+ } else if (ext === '.cjs') {
87
+ runCmd = `if [ -f "${file}" ]; then\n bash bin/test-cjs --testPathPatterns="${filename}"\nelse\n echo "Skipping missing test file: ${file}"\nfi`;
88
+ } else if (ext === '.ts') {
89
+ runCmd = `if [ -f "${file}" ]; then\n node node_modules/jest/bin/jest.js --runInBand --forceExit --testTimeout=120000 --detectOpenHandles --testPathPatterns="${filename}"\nelse\n echo "Skipping missing test file: ${file}"\nfi`;
90
+ } else {
91
+ runCmd = `if [ -f "${file}" ]; then\n npm test -- --testPathPatterns="${filename}"\nelse\n echo "Skipping missing test file: ${file}"\nfi`;
92
+ }
93
+
94
+ // Skip files that are not tracked by git (untracked files shouldn't be included in committed actions)
95
+ try {
96
+ execSync(`git ls-files --error-unmatch -- "${file}"`, { cwd: process.cwd(), stdio: 'ignore' });
97
+ } catch {
98
+ console.warn(`Skipping untracked test file: ${file}`);
99
+ continue;
100
+ }
101
+
102
+ const rawId = file;
103
+ let safeId = rawId.replace(/[^A-Za-z0-9_-]+/g, '-');
104
+ if (!/^[A-Za-z_]/.test(safeId)) safeId = `_${safeId}`;
105
+ safeId = (safeId.slice(0, 90) || `test-${Math.random().toString(36).slice(2, 8)}`).replace(/-(test|spec)$/, '');
106
+
107
+ actionObject.jobs.ci.steps.push({
108
+ name: `🧪 Run tests in ${file}`,
109
+ id: `run-${safeId}`,
110
+ shell: 'bash',
111
+ run: runCmd
112
+ });
113
+ }
114
+
115
+ const yamlContent = yaml.stringify(actionObject);
116
+ fs.ensureDirSync(path.dirname(actionFile));
117
+ fs.writeFileSync(actionFile, yamlContent, 'utf-8');
118
+ try {
119
+ execSync(`npx -y prettier@latest -w "${actionFile}"`, { stdio: 'inherit', cwd: process.cwd() });
120
+ } catch {
121
+ // prettier is optional — skip formatting if unavailable
122
+ }
123
+ console.log(`Generated ${actionFile} with ${files.length} test steps.`);
124
+ }
125
+
126
+ main();
@@ -0,0 +1,365 @@
1
+ # clue/reactphp-ndjson
2
+
3
+ [![CI status](https://github.com/clue/reactphp-ndjson/actions/workflows/ci.yml/badge.svg)](https://github.com/clue/reactphp-ndjson/actions)
4
+ [![installs on Packagist](https://img.shields.io/packagist/dt/clue/ndjson-react?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/ndjson-react)
5
+ [![code coverage](https://img.shields.io/badge/code%20coverage-100%25-success)](#tests)
6
+
7
+ Streaming newline-delimited JSON ([NDJSON](http://ndjson.org/)) parser and encoder for [ReactPHP](https://reactphp.org/).
8
+
9
+ [NDJSON](http://ndjson.org/) can be used to store multiple JSON records in a
10
+ file to store any kind of (uniform) structured data, such as a list of user
11
+ objects or log entries. It uses a simple newline character between each
12
+ individual record and as such can be both used for efficient persistence and
13
+ simple append-style operations. This also allows it to be used in a streaming
14
+ context, such as a simple inter-process communication (IPC) protocol or for a
15
+ remote procedure call (RPC) mechanism. This library provides a simple
16
+ streaming API to process very large NDJSON files with thousands or even millions
17
+ of rows efficiently without having to load the whole file into memory at once.
18
+
19
+ * **Standard interfaces** -
20
+ Allows easy integration with existing higher-level components by implementing
21
+ ReactPHP's standard streaming interfaces.
22
+ * **Lightweight, SOLID design** -
23
+ Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
24
+ and does not get in your way.
25
+ Builds on top of well-tested components and well-established concepts instead of reinventing the wheel.
26
+ * **Good test coverage** -
27
+ Comes with an [automated tests suite](#tests) and is regularly tested in the *real world*.
28
+
29
+ **Table of contents**
30
+
31
+ * [Support us](#support-us)
32
+ * [NDJSON format](#ndjson-format)
33
+ * [Usage](#usage)
34
+ * [Decoder](#decoder)
35
+ * [Encoder](#encoder)
36
+ * [Install](#install)
37
+ * [Tests](#tests)
38
+ * [License](#license)
39
+ * [More](#more)
40
+
41
+ ## Support us
42
+
43
+ We invest a lot of time developing, maintaining, and updating our awesome
44
+ open-source projects. You can help us sustain this high-quality of our work by
45
+ [becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
46
+ numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
47
+ for details.
48
+
49
+ Let's take these projects to the next level together! 🚀
50
+
51
+ ## NDJSON format
52
+
53
+ NDJSON ("Newline-Delimited JSON" or sometimes referred to as "JSON lines") is a
54
+ very simple text-based format for storing a large number of records, such as a
55
+ list of user records or log entries.
56
+
57
+ ```JSON
58
+ {"name":"Alice","age":30,"comment":"Yes, I like cheese"}
59
+ {"name":"Bob","age":50,"comment":"Hello\nWorld!"}
60
+ ```
61
+
62
+ If you understand JSON and you're now looking at this newline-delimited JSON for
63
+ the first time, you should already know everything you need to know to
64
+ understand NDJSON: As the name implies, this format essentially consists of
65
+ individual lines where each individual line is any valid JSON text and each line
66
+ is delimited with a newline character.
67
+
68
+ This example uses a list of user objects where each user has some arbitrary
69
+ properties. This can easily be adjusted for many different use cases, such as
70
+ storing for example products instead of users, assigning additional properties
71
+ or having a significantly larger number of records. You can edit NDJSON files in
72
+ any text editor or use them in a streaming context where individual records
73
+ should be processed. Unlike normal JSON files, adding a new log entry to this
74
+ NDJSON file does not require modification of this file's structure (note there's
75
+ no "outer array" to be modified). This makes it a perfect fit for a streaming
76
+ context, for line-oriented CLI tools (such as `grep` and others) or for a logging
77
+ context where you want to append records at a later time. Additionally, this
78
+ also allows it to be used in a streaming context, such as a simple inter-process
79
+ communication (IPC) protocol or for a remote procedure call (RPC) mechanism.
80
+
81
+ The newline character at the end of each line allows for some really simple
82
+ *framing* (detecting individual records). While each individual line is valid
83
+ JSON, the complete file as a whole is technically no longer valid JSON, because
84
+ it contains multiple JSON texts. This implies that for example calling PHP's
85
+ `json_decode()` on this complete input would fail because it would try to parse
86
+ multiple records at once. Likewise, using "pretty printing" JSON
87
+ (`JSON_PRETTY_PRINT`) is not allowed because each JSON text is limited to exactly
88
+ one line. On the other hand, values containing newline characters (such as the
89
+ `comment` property in the above example) do not cause issues because each newline
90
+ within a JSON string will be represented by a `\n` instead.
91
+
92
+ One common alternative to NDJSON would be Comma-Separated Values (CSV).
93
+ If you want to process CSV files, you may want to take a look at the related
94
+ project [clue/reactphp-csv](https://github.com/clue/reactphp-csv) instead:
95
+
96
+ ```
97
+ name,age,comment
98
+ Alice,30,"Yes, I like cheese"
99
+ Bob,50,"Hello
100
+ World!"
101
+ ```
102
+
103
+ CSV may look slightly simpler, but this simplicity comes at a price. CSV is
104
+ limited to untyped, two-dimensional data, so there's no standard way of storing
105
+ any nested structures or to differentiate a boolean value from a string or
106
+ integer. Field names are sometimes used, sometimes they're not
107
+ (application-dependant). Inconsistent handling for fields that contain
108
+ separators such as `,` or spaces or line breaks (see the `comment` field above)
109
+ introduce additional complexity and its text encoding is usually undefined,
110
+ Unicode (or UTF-8) is unlikely to be supported and CSV files often use ISO
111
+ 8859-1 encoding or some variant (again application-dependant).
112
+
113
+ While NDJSON helps avoiding many of CSV's shortcomings, it is still a
114
+ (relatively) young format while CSV files have been used in production systems
115
+ for decades. This means that if you want to interface with an existing system,
116
+ you may have to rely on the format that's already supported. If you're building
117
+ a new system, using NDJSON is an excellent choice as it provides a flexible way
118
+ to process individual records using a common text-based format that can include
119
+ any kind of structured data.
120
+
121
+ ## Usage
122
+
123
+ ### Decoder
124
+
125
+ The `Decoder` (parser) class can be used to make sure you only get back
126
+ complete, valid JSON elements when reading from a stream.
127
+ It wraps a given
128
+ [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface)
129
+ and exposes its data through the same interface, but emits the JSON elements
130
+ as parsed values instead of just chunks of strings:
131
+
132
+ ```
133
+ {"name":"test","active":true}
134
+ {"name":"hello w\u00f6rld","active":true}
135
+ ```
136
+
137
+ ```php
138
+ $stdin = new React\Stream\ReadableResourceStream(STDIN);
139
+
140
+ $ndjson = new Clue\React\NDJson\Decoder($stdin);
141
+
142
+ $ndjson->on('data', function ($data) {
143
+ // $data is a parsed element from the JSON stream
144
+ // line 1: $data = (object)array('name' => 'test', 'active' => true);
145
+ // line 2: $data = (object)array('name' => 'hello wörld', 'active' => true);
146
+ var_dump($data);
147
+ });
148
+ ```
149
+
150
+ ReactPHP's streams emit chunks of data strings and make no assumption about their lengths.
151
+ These chunks do not necessarily represent complete JSON elements, as an
152
+ element may be broken up into multiple chunks.
153
+ This class reassembles these elements by buffering incomplete ones.
154
+
155
+ The `Decoder` supports the same optional parameters as the underlying
156
+ [`json_decode()`](https://www.php.net/manual/en/function.json-decode.php) function.
157
+ This means that, by default, JSON objects will be emitted as a `stdClass`.
158
+ This behavior can be controlled through the optional constructor parameters:
159
+
160
+ ```php
161
+ $ndjson = new Clue\React\NDJson\Decoder($stdin, true);
162
+
163
+ $ndjson->on('data', function ($data) {
164
+ // JSON objects will be emitted as assoc arrays now
165
+ });
166
+ ```
167
+
168
+ Additionally, the `Decoder` limits the maximum buffer size (maximum line
169
+ length) to avoid buffer overflows due to malformed user input. Usually, there
170
+ should be no need to change this value, unless you know you're dealing with some
171
+ unreasonably long lines. It accepts an additional argument if you want to change
172
+ this from the default of 64 KiB:
173
+
174
+ ```php
175
+ $ndjson = new Clue\React\NDJson\Decoder($stdin, false, 512, 0, 64 * 1024);
176
+ ```
177
+
178
+ If the underlying stream emits an `error` event or the plain stream contains
179
+ any data that does not represent a valid NDJson stream,
180
+ it will emit an `error` event and then `close` the input stream:
181
+
182
+ ```php
183
+ $ndjson->on('error', function (Exception $error) {
184
+ // an error occured, stream will close next
185
+ });
186
+ ```
187
+
188
+ If the underlying stream emits an `end` event, it will flush any incomplete
189
+ data from the buffer, thus either possibly emitting a final `data` event
190
+ followed by an `end` event on success or an `error` event for
191
+ incomplete/invalid JSON data as above:
192
+
193
+ ```php
194
+ $ndjson->on('end', function () {
195
+ // stream successfully ended, stream will close next
196
+ });
197
+ ```
198
+
199
+ If either the underlying stream or the `Decoder` is closed, it will forward
200
+ the `close` event:
201
+
202
+ ```php
203
+ $ndjson->on('close', function () {
204
+ // stream closed
205
+ // possibly after an "end" event or due to an "error" event
206
+ });
207
+ ```
208
+
209
+ The `close(): void` method can be used to explicitly close the `Decoder` and
210
+ its underlying stream:
211
+
212
+ ```php
213
+ $ndjson->close();
214
+ ```
215
+
216
+ The `pipe(WritableStreamInterface $dest, array $options = array(): WritableStreamInterface`
217
+ method can be used to forward all data to the given destination stream.
218
+ Please note that the `Decoder` emits decoded/parsed data events, while many
219
+ (most?) writable streams expect only data chunks:
220
+
221
+ ```php
222
+ $ndjson->pipe($logger);
223
+ ```
224
+
225
+ For more details, see ReactPHP's
226
+ [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface).
227
+
228
+ ### Encoder
229
+
230
+ The `Encoder` (serializer) class can be used to make sure anything you write to
231
+ a stream ends up as valid JSON elements in the resulting NDJSON stream.
232
+ It wraps a given
233
+ [`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface)
234
+ and accepts its data through the same interface, but handles any data as complete
235
+ JSON elements instead of just chunks of strings:
236
+
237
+ ```php
238
+ $stdout = new React\Stream\WritableResourceStream(STDOUT);
239
+
240
+ $ndjson = new Clue\React\NDJson\Encoder($stdout);
241
+
242
+ $ndjson->write(array('name' => 'test', 'active' => true));
243
+ $ndjson->write(array('name' => 'hello wörld', 'active' => true));
244
+ ```
245
+ ```
246
+ {"name":"test","active":true}
247
+ {"name":"hello w\u00f6rld","active":true}
248
+ ```
249
+
250
+ The `Encoder` supports the same parameters as the underlying
251
+ [`json_encode()`](https://www.php.net/manual/en/function.json-encode.php) function.
252
+ This means that, by default, Unicode characters will be escaped in the output.
253
+ This behavior can be controlled through the optional constructor parameters:
254
+
255
+ ```php
256
+ $ndjson = new Clue\React\NDJson\Encoder($stdout, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
257
+
258
+ $ndjson->write('hello wörld');
259
+ ```
260
+ ```
261
+ "hello wörld"
262
+ ```
263
+
264
+ Note that trying to pass the `JSON_PRETTY_PRINT` option will yield an
265
+ `InvalidArgumentException` because it is not compatible with NDJSON.
266
+
267
+ If the underlying stream emits an `error` event or the given data contains
268
+ any data that can not be represented as a valid NDJSON stream,
269
+ it will emit an `error` event and then `close` the input stream:
270
+
271
+ ```php
272
+ $ndjson->on('error', function (Exception $error) {
273
+ // an error occured, stream will close next
274
+ });
275
+ ```
276
+
277
+ If either the underlying stream or the `Encoder` is closed, it will forward
278
+ the `close` event:
279
+
280
+ ```php
281
+ $ndjson->on('close', function () {
282
+ // stream closed
283
+ // possibly after an "end" event or due to an "error" event
284
+ });
285
+ ```
286
+
287
+ The `end(mixed $data = null): void` method can be used to optionally emit
288
+ any final data and then soft-close the `Encoder` and its underlying stream:
289
+
290
+ ```php
291
+ $ndjson->end();
292
+ ```
293
+
294
+ The `close(): void` method can be used to explicitly close the `Encoder` and
295
+ its underlying stream:
296
+
297
+ ```php
298
+ $ndjson->close();
299
+ ```
300
+
301
+ For more details, see ReactPHP's
302
+ [`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface).
303
+
304
+ ## Install
305
+
306
+ The recommended way to install this library is [through Composer](https://getcomposer.org/).
307
+ [New to Composer?](https://getcomposer.org/doc/00-intro.md)
308
+
309
+ This project follows [SemVer](https://semver.org/).
310
+ This will install the latest supported version:
311
+
312
+ ```bash
313
+ composer require clue/ndjson-react:^1.3
314
+ ```
315
+
316
+ See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
317
+
318
+ This project aims to run on any platform and thus does not require any PHP
319
+ extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
320
+ HHVM.
321
+ It's *highly recommended to use the latest supported PHP version* for this project.
322
+
323
+ ## Tests
324
+
325
+ To run the test suite, you first need to clone this repo and then install all
326
+ dependencies [through Composer](https://getcomposer.org/):
327
+
328
+ ```bash
329
+ composer install
330
+ ```
331
+
332
+ To run the test suite, go to the project root and run:
333
+
334
+ ```bash
335
+ vendor/bin/phpunit
336
+ ```
337
+
338
+ ## License
339
+
340
+ This project is released under the permissive [MIT license](LICENSE).
341
+
342
+ > Did you know that I offer custom development services and issuing invoices for
343
+ sponsorships of releases and for contributions? Contact me (@clue) for details.
344
+
345
+ ## More
346
+
347
+ * If you want to learn more about processing streams of data, refer to the documentation of
348
+ the underlying [react/stream](https://github.com/reactphp/stream) component.
349
+
350
+ * If you want to process compressed NDJSON files (`.ndjson.gz` file extension),
351
+ you may want to use [clue/reactphp-zlib](https://github.com/clue/reactphp-zlib)
352
+ on the compressed input stream before passing the decompressed stream to the NDJSON decoder.
353
+
354
+ * If you want to create compressed NDJSON files (`.ndjson.gz` file extension),
355
+ you may want to use [clue/reactphp-zlib](https://github.com/clue/reactphp-zlib)
356
+ on the resulting NDJSON encoder output stream before passing the compressed
357
+ stream to the file output stream.
358
+
359
+ * If you want to concurrently process the records from your NDJSON stream,
360
+ you may want to use [clue/reactphp-flux](https://github.com/clue/reactphp-flux)
361
+ to concurrently process many (but not too many) records at once.
362
+
363
+ * If you want to process structured data in the more common text-based format,
364
+ you may want to use [clue/reactphp-csv](https://github.com/clue/reactphp-csv)
365
+ to process Comma-Separated-Values (CSV) files (`.csv` file extension).
@@ -0,0 +1,189 @@
1
+ composer/pcre
2
+ =============
3
+
4
+ PCRE wrapping library that offers type-safe `preg_*` replacements.
5
+
6
+ This library gives you a way to ensure `preg_*` functions do not fail silently, returning
7
+ unexpected `null`s that may not be handled.
8
+
9
+ As of 3.0 this library enforces [`PREG_UNMATCHED_AS_NULL`](#preg_unmatched_as_null) usage
10
+ for all matching and replaceCallback functions, [read more below](#preg_unmatched_as_null)
11
+ to understand the implications.
12
+
13
+ It thus makes it easier to work with static analysis tools like PHPStan or Psalm as it
14
+ simplifies and reduces the possible return values from all the `preg_*` functions which
15
+ are quite packed with edge cases. As of v2.2.0 / v3.2.0 the library also comes with a
16
+ [PHPStan extension](#phpstan-extension) for parsing regular expressions and giving you even better output types.
17
+
18
+ This library is a thin wrapper around `preg_*` functions with [some limitations](#restrictions--limitations).
19
+ If you are looking for a richer API to handle regular expressions have a look at
20
+ [rawr/t-regx](https://packagist.org/packages/rawr/t-regx) instead.
21
+
22
+ [![Continuous Integration](https://github.com/composer/pcre/workflows/Continuous%20Integration/badge.svg?branch=main)](https://github.com/composer/pcre/actions)
23
+
24
+
25
+ Installation
26
+ ------------
27
+
28
+ Install the latest version with:
29
+
30
+ ```bash
31
+ $ composer require composer/pcre
32
+ ```
33
+
34
+
35
+ Requirements
36
+ ------------
37
+
38
+ * PHP 7.4.0 is required for 3.x versions
39
+ * PHP 7.2.0 is required for 2.x versions
40
+ * PHP 5.3.2 is required for 1.x versions
41
+
42
+
43
+ Basic usage
44
+ -----------
45
+
46
+ Instead of:
47
+
48
+ ```php
49
+ if (preg_match('{fo+}', $string, $matches)) { ... }
50
+ if (preg_match('{fo+}', $string, $matches, PREG_OFFSET_CAPTURE)) { ... }
51
+ if (preg_match_all('{fo+}', $string, $matches)) { ... }
52
+ $newString = preg_replace('{fo+}', 'bar', $string);
53
+ $newString = preg_replace_callback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string);
54
+ $newString = preg_replace_callback_array(['{fo+}' => fn ($match) => strtoupper($match[0])], $string);
55
+ $filtered = preg_grep('{[a-z]}', $elements);
56
+ $array = preg_split('{[a-z]+}', $string);
57
+ ```
58
+
59
+ You can now call these on the `Preg` class:
60
+
61
+ ```php
62
+ use Composer\Pcre\Preg;
63
+
64
+ if (Preg::match('{fo+}', $string, $matches)) { ... }
65
+ if (Preg::matchWithOffsets('{fo+}', $string, $matches)) { ... }
66
+ if (Preg::matchAll('{fo+}', $string, $matches)) { ... }
67
+ $newString = Preg::replace('{fo+}', 'bar', $string);
68
+ $newString = Preg::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string);
69
+ $newString = Preg::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string);
70
+ $filtered = Preg::grep('{[a-z]}', $elements);
71
+ $array = Preg::split('{[a-z]+}', $string);
72
+ ```
73
+
74
+ The main difference is if anything fails to match/replace/.., it will throw a `Composer\Pcre\PcreException`
75
+ instead of returning `null` (or false in some cases), so you can now use the return values safely relying on
76
+ the fact that they can only be strings (for replace), ints (for match) or arrays (for grep/split).
77
+
78
+ Additionally the `Preg` class provides match methods that return `bool` rather than `int`, for stricter type safety
79
+ when the number of pattern matches is not useful:
80
+
81
+ ```php
82
+ use Composer\Pcre\Preg;
83
+
84
+ if (Preg::isMatch('{fo+}', $string, $matches)) // bool
85
+ if (Preg::isMatchAll('{fo+}', $string, $matches)) // bool
86
+ ```
87
+
88
+ Finally the `Preg` class provides a few `*StrictGroups` method variants that ensure match groups
89
+ are always present and thus non-nullable, making it easier to write type-safe code:
90
+
91
+ ```php
92
+ use Composer\Pcre\Preg;
93
+
94
+ // $matches is guaranteed to be an array of strings, if a subpattern does not match and produces a null it will throw
95
+ if (Preg::matchStrictGroups('{fo+}', $string, $matches))
96
+ if (Preg::matchAllStrictGroups('{fo+}', $string, $matches))
97
+ ```
98
+
99
+ **Note:** This is generally safe to use as long as you do not have optional subpatterns (i.e. `(something)?`
100
+ or `(something)*` or branches with a `|` that result in some groups not being matched at all).
101
+ A subpattern that can match an empty string like `(.*)` is **not** optional, it will be present as an
102
+ empty string in the matches. A non-matching subpattern, even if optional like `(?:foo)?` will anyway not be present in
103
+ matches so it is also not a problem to use these with `*StrictGroups` methods.
104
+
105
+ If you would prefer a slightly more verbose usage, replacing by-ref arguments by result objects, you can use the `Regex` class:
106
+
107
+ ```php
108
+ use Composer\Pcre\Regex;
109
+
110
+ // this is useful when you are just interested in knowing if something matched
111
+ // as it returns a bool instead of int(1/0) for match
112
+ $bool = Regex::isMatch('{fo+}', $string);
113
+
114
+ $result = Regex::match('{fo+}', $string);
115
+ if ($result->matched) { something($result->matches); }
116
+
117
+ $result = Regex::matchWithOffsets('{fo+}', $string);
118
+ if ($result->matched) { something($result->matches); }
119
+
120
+ $result = Regex::matchAll('{fo+}', $string);
121
+ if ($result->matched && $result->count > 3) { something($result->matches); }
122
+
123
+ $newString = Regex::replace('{fo+}', 'bar', $string)->result;
124
+ $newString = Regex::replaceCallback('{fo+}', function ($match) { return strtoupper($match[0]); }, $string)->result;
125
+ $newString = Regex::replaceCallbackArray(['{fo+}' => fn ($match) => strtoupper($match[0])], $string)->result;
126
+ ```
127
+
128
+ Note that `preg_grep` and `preg_split` are only callable via the `Preg` class as they do not have
129
+ complex return types warranting a specific result object.
130
+
131
+ See the [MatchResult](src/MatchResult.php), [MatchWithOffsetsResult](src/MatchWithOffsetsResult.php), [MatchAllResult](src/MatchAllResult.php),
132
+ [MatchAllWithOffsetsResult](src/MatchAllWithOffsetsResult.php), and [ReplaceResult](src/ReplaceResult.php) class sources for more details.
133
+
134
+ Restrictions / Limitations
135
+ --------------------------
136
+
137
+ Due to type safety requirements a few restrictions are in place.
138
+
139
+ - matching using `PREG_OFFSET_CAPTURE` is made available via `matchWithOffsets` and `matchAllWithOffsets`.
140
+ You cannot pass the flag to `match`/`matchAll`.
141
+ - `Preg::split` will also reject `PREG_SPLIT_OFFSET_CAPTURE` and you should use `splitWithOffsets`
142
+ instead.
143
+ - `matchAll` rejects `PREG_SET_ORDER` as it also changes the shape of the returned matches. There
144
+ is no alternative provided as you can fairly easily code around it.
145
+ - `preg_filter` is not supported as it has a rather crazy API, most likely you should rather
146
+ use `Preg::grep` in combination with some loop and `Preg::replace`.
147
+ - `replace`, `replaceCallback` and `replaceCallbackArray` do not support an array `$subject`,
148
+ only simple strings.
149
+ - As of 2.0, the library always uses `PREG_UNMATCHED_AS_NULL` for matching, which offers [much
150
+ saner/more predictable results](#preg_unmatched_as_null). As of 3.0 the flag is also set for
151
+ `replaceCallback` and `replaceCallbackArray`.
152
+
153
+ #### PREG_UNMATCHED_AS_NULL
154
+
155
+ As of 2.0, this library always uses PREG_UNMATCHED_AS_NULL for all `match*` and `isMatch*`
156
+ functions. As of 3.0 it is also done for `replaceCallback` and `replaceCallbackArray`.
157
+
158
+ This means your matches will always contain all matching groups, either as null if unmatched
159
+ or as string if it matched.
160
+
161
+ The advantages in clarity and predictability are clearer if you compare the two outputs of
162
+ running this with and without PREG_UNMATCHED_AS_NULL in $flags:
163
+
164
+ ```php
165
+ preg_match('/(a)(b)*(c)(d)*/', 'ac', $matches, $flags);
166
+ ```
167
+
168
+ | no flag | PREG_UNMATCHED_AS_NULL |
169
+ | --- | --- |
170
+ | array (size=4) | array (size=5) |
171
+ | 0 => string 'ac' (length=2) | 0 => string 'ac' (length=2) |
172
+ | 1 => string 'a' (length=1) | 1 => string 'a' (length=1) |
173
+ | 2 => string '' (length=0) | 2 => null |
174
+ | 3 => string 'c' (length=1) | 3 => string 'c' (length=1) |
175
+ | | 4 => null |
176
+ | group 2 (any unmatched group preceding one that matched) is set to `''`. You cannot tell if it matched an empty string or did not match at all | group 2 is `null` when unmatched and a string if it matched, easy to check for |
177
+ | group 4 (any optional group without a matching one following) is missing altogether. So you have to check with `isset()`, but really you want `isset($m[4]) && $m[4] !== ''` for safety unless you are very careful to check that a non-optional group follows it | group 4 is always set, and null in this case as there was no match, easy to check for with `$m[4] !== null` |
178
+
179
+ PHPStan Extension
180
+ -----------------
181
+
182
+ To use the PHPStan extension if you do not use `phpstan/extension-installer` you can include `vendor/composer/pcre/extension.neon` in your PHPStan config.
183
+
184
+ The extension provides much better type information for $matches as well as regex validation where possible.
185
+
186
+ License
187
+ -------
188
+
189
+ composer/pcre is licensed under the MIT License, see the LICENSE file for details.