From 31b41a3857dfc84279c14619fe2d4a6a2ec7dc5b Mon Sep 17 00:00:00 2001 From: Kuba Orlik Date: Sun, 10 Dec 2023 15:15:13 +0100 Subject: [PATCH] Working cli and browser versions --- README.md | 9 + captain-definition | 4 + package-lock.json | 525 ++++++++++++++++++++++++++++++++++++++++--- package.json | 22 +- public/bundle.js | 22 ++ public/bundle.js.map | 7 + public/index.html | 40 ++++ src/browser.ts | 44 ++++ src/cli.ts | 57 +++++ src/example.test.ts | 11 - src/index.ts | 187 ++++++++++++++- src/utils.ts | 28 +++ tsconfig.json | 6 +- 13 files changed, 907 insertions(+), 55 deletions(-) create mode 100644 README.md create mode 100644 captain-definition create mode 100644 public/bundle.js create mode 100644 public/bundle.js.map create mode 100644 public/index.html create mode 100644 src/browser.ts create mode 100644 src/cli.ts create mode 100644 src/utils.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa303ad --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Mbank mt940 converter + +## Testowanie + +Wygeneruj raz plik mt940 oraz csv za ten sam okres, i wykonaj: + +``` +node . convert < tests/real_csv.csv > tests/new.txt && diff --side-by-side --color tests/real_mt940.txt tests/new.txt +``` diff --git a/captain-definition b/captain-definition new file mode 100644 index 0000000..6a86494 --- /dev/null +++ b/captain-definition @@ -0,0 +1,4 @@ +{ + "schemaVersion": 2, + "templateId": "node/20" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d522aff..bf93fe8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,25 @@ { - "name": "module-starter", - "version": "0.0.1", + "name": "mbank-mt940", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "module-starter", - "version": "0.0.1", + "name": "mbank-mt940", + "version": "0.1.0", "license": "ISC", + "dependencies": { + "iconv": "^3.0.1", + "iconv-lite": "^0.6.3", + "yargs": "^17.7.2" + }, "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/mocha": "^10.0.1", + "@types/yargs": "^17.0.32", "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", + "esbuild": "^0.19.9", "eslint": "^8.38.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", @@ -473,6 +480,358 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.9.tgz", + "integrity": "sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.9.tgz", + "integrity": "sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.9.tgz", + "integrity": "sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.9.tgz", + "integrity": "sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.9.tgz", + "integrity": "sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.9.tgz", + "integrity": "sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.9.tgz", + "integrity": "sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.9.tgz", + "integrity": "sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.9.tgz", + "integrity": "sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.9.tgz", + "integrity": "sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.9.tgz", + "integrity": "sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.9.tgz", + "integrity": "sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.9.tgz", + "integrity": "sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.9.tgz", + "integrity": "sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.9.tgz", + "integrity": "sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.9.tgz", + "integrity": "sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.9.tgz", + "integrity": "sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.9.tgz", + "integrity": "sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.9.tgz", + "integrity": "sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.9.tgz", + "integrity": "sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.9.tgz", + "integrity": "sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.9.tgz", + "integrity": "sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -831,6 +1190,21 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.58.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz", @@ -1406,7 +1780,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1415,7 +1788,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1664,21 +2036,22 @@ } }, "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1689,8 +2062,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/commondir": { "version": "1.0.1", @@ -1825,8 +2197,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/es6-error": { "version": "4.1.1", @@ -1834,11 +2205,47 @@ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "node_modules/esbuild": { + "version": "0.19.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.9.tgz", + "integrity": "sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.19.9", + "@esbuild/android-arm64": "0.19.9", + "@esbuild/android-x64": "0.19.9", + "@esbuild/darwin-arm64": "0.19.9", + "@esbuild/darwin-x64": "0.19.9", + "@esbuild/freebsd-arm64": "0.19.9", + "@esbuild/freebsd-x64": "0.19.9", + "@esbuild/linux-arm": "0.19.9", + "@esbuild/linux-arm64": "0.19.9", + "@esbuild/linux-ia32": "0.19.9", + "@esbuild/linux-loong64": "0.19.9", + "@esbuild/linux-mips64el": "0.19.9", + "@esbuild/linux-ppc64": "0.19.9", + "@esbuild/linux-riscv64": "0.19.9", + "@esbuild/linux-s390x": "0.19.9", + "@esbuild/linux-x64": "0.19.9", + "@esbuild/netbsd-x64": "0.19.9", + "@esbuild/openbsd-x64": "0.19.9", + "@esbuild/sunos-x64": "0.19.9", + "@esbuild/win32-arm64": "0.19.9", + "@esbuild/win32-ia32": "0.19.9", + "@esbuild/win32-x64": "0.19.9" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -2376,7 +2783,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -2518,6 +2924,26 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/iconv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/iconv/-/iconv-3.0.1.tgz", + "integrity": "sha512-lJnFLxVc0d82R7GfU7a9RujKVUQ3Eee19tPKWZWBJtAEGRHVEyFzCtbNl3GPKuDnHBBRT4/nDS4Ru9AIDT72qA==", + "hasInstallScript": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -2602,7 +3028,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -3052,6 +3477,17 @@ "balanced-match": "^1.0.0" } }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/mocha/node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -3097,6 +3533,24 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -3667,7 +4121,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3755,6 +4208,11 @@ } ] }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/semver": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz", @@ -3885,7 +4343,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -3899,7 +4356,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4190,7 +4646,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -4225,7 +4680,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -4237,21 +4691,20 @@ "dev": true }, "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=10" + "node": ">=12" } }, "node_modules/yargs-parser": { @@ -4302,6 +4755,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 0944595..e20a7bd 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,29 @@ { - "name": "module-starter", - "version": "0.0.1", + "name": "mbank-mt940", + "version": "0.1.0", "description": "module template", - "main": "lib/index.js", + "main": "lib/cli.js", "scripts": { "test": "mocha", - "build": "tsc", + "build": "npm run build-node && npm run build-browser", + "build-node": "tsc", + "build-browser": "esbuild src/browser.ts --bundle --minify --sourcemap --target=firefox120 --outfile=public/bundle.js", "prepare": "npm run build", "clean-coverage": "rm -rf coverage .nyc_output .xunit", "coverage": "npm run clean-coverage && nyc mocha", "test-reports": "npm run clean-coverage && nyc --reporter clover mocha --reporter xunit --reporter-option output=.xunit", - "coverage-html": "npm run test-reports && nyc report --reporter lcov && xdg-open coverage/lcov-report/index.html" + "coverage-html": "npm run test-reports && nyc report --reporter lcov && xdg-open coverage/lcov-report/index.html", + "start": "http-server public -p 8080" }, "author": "", "license": "ISC", "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", "@types/mocha": "^10.0.1", + "@types/yargs": "^17.0.32", "@typescript-eslint/eslint-plugin": "^5.58.0", "@typescript-eslint/parser": "^5.58.0", + "esbuild": "^0.19.9", "eslint": "^8.38.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", @@ -31,5 +36,10 @@ "ts-node": "^10.9.1", "typescript": "^5.0.4" }, - "types": "./@types/index.d.ts" + "types": "./@types/index.d.ts", + "dependencies": { + "iconv": "^3.0.1", + "iconv-lite": "^0.6.3", + "yargs": "^17.7.2" + } } diff --git a/public/bundle.js b/public/bundle.js new file mode 100644 index 0000000..aa653e7 --- /dev/null +++ b/public/bundle.js @@ -0,0 +1,22 @@ +(()=>{function l(e,n){let t=[];for(let o=0;o`${t}${n+r}${o}`)}function _(e){return e.replace(/ +/g," ")}var g=class{constructor(n,t,o,r){this.number=n;this.initial_balance=t;this.closing_balance=o;this.currency=r}},d=class e{constructor(n,t,o,r,s,i,c,a){this.acc_date=n;this.op_date=t;this.description=o;this.title=r;this.person=s;this.account_number=i;this.amount=c;this.balance_after=a}static{this.counter=function*(){let n=1;for(;;)yield n.toString().padStart(11,"0"),n++}()}formatTitle(){return m(p(l(this.title.trim(),27),9),20).join(` +`)}formatPerson(){return m(p(l(_(this.person).trim(),27),2).slice(0,2),32).join(` +`)}toMT940(){let n={incoming:"150",outcoming:"169"},t=this.amount>0?n.incoming:n.outcoming;return`:61:${u(this.acc_date)}${u(this.op_date).slice(2)}C${$(this.amount)}S${t}${e.counter.next().value} +:86:${t} +:86:${t}~00B${t}${this.description.slice(0,23)} +${this.formatTitle()} +~29${this.account_number} +~30${this.account_number.slice(2,10)} +~31${this.account_number.slice(10)} +${this.formatPerson()} +~34${t} +~38PL${this.account_number} +~62 +~63`}},b=class{constructor(n,t){this.date_start=n;this.date_end=t}},h=class{},f=class extends h{read_csv(n){let t=n.split(`\r +`).map(c=>c.split(";")),o=0;for(;!t[o][0].startsWith("Niniejszy dokument sporz\u0105dzono");)o++;let r=new g(t[20][0],this.parseAmount(t[35][1]),this.parseAmount(t[o-2][7]),t[18][0]),s=new b(this.parseDate(t[14][0]),this.parseDate(t[14][1])),i=[];for(let c=38;c<=o-5;c++){let a=t[c];if(a.length!=9)throw new Error("Wrong amount of columns! maybe a semicolon got stuck in a transaction description?");let v=new Date(a[0]),y=new Date(a[1]),[D,S,x,L]=a.slice(2).map(this.trimString),[M,E]=a.slice(6).map(T=>this.parseAmount(T));i.push(new d(v,y,D,S,x,L,M,E))}return{account:r,range:s,transactions:i}}parseAmount(n){return parseFloat(n.replace(/[^0-9,-]/g,"").replace(",","."))}parseDate(n){let[t,o,r]=n.split(".").map(i=>parseInt(i)),s=new Date;return s.setHours(0),s.setMinutes(0),s.setSeconds(0),s.setMilliseconds(0),s.setDate(t),s.setMonth(o-1),s.setFullYear(r),s}trimString(n){return n.replaceAll(/(^["']|["']$)/g,"")}};function u(e){return`${e.getFullYear()-2e3}${(e.getMonth()+1).toString().padStart(2,"0")}${e.getDate().toString().padStart(2,"0")}`}function $(e){return Math.abs(e).toString().replace(".",",")}function w(e){let n=new f().read_csv(e),{account:t,transactions:o,range:r}=n;return{output:`:20:MT940 +:25:/PL${t.number.replaceAll(/[^0-9]/g,"")} +:28C:${u(r.date_start)} +:60F:D${u(r.date_start)}${t.currency}${$(t.initial_balance)} +${o.map(i=>i.toMT940()).join(` +`)} +:62F:D${u(r.date_end)}${t.currency}${$(t.closing_balance)}`,range:r}}function A(e,n,t){var o=new Blob([e],{type:t}),r=document.createElement("a"),s=URL.createObjectURL(o);r.href=s,r.download=n,document.body.appendChild(r),r.click(),setTimeout(function(){document.body.removeChild(r),window.URL.revokeObjectURL(s)},0)}async function R(){console.log("browser.ts:2");let e=document.getElementById("csv");if(!e||!(e instanceof HTMLInputElement))throw console.log("browser.ts:5"),new Error("coudl not find the csv input");var n=e.value.replace("C:\\fakepath\\","");let t=e.files;if(!t)throw new Error("no files in the input");let o=await t[0].arrayBuffer(),r=new TextDecoder("windows-1250").decode(o),s=w(r);A(s.output,`raport-${s.range.date_start.getFullYear()}-${(s.range.date_start.getMonth()+1).toString().padStart(2,"0")}.mt940`,"text")}document.querySelector("#submit")?.addEventListener("click",R);console.log("added handler to",document.querySelector("#submit"));})(); +//# sourceMappingURL=bundle.js.map diff --git a/public/bundle.js.map b/public/bundle.js.map new file mode 100644 index 0000000..c6efb47 --- /dev/null +++ b/public/bundle.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../src/utils.ts", "../src/index.ts", "../src/browser.ts"], + "sourcesContent": ["export function chunks(s: string, chunkSize: number): string[] {\n\tconst result = [];\n\tfor (let i = 0; i < s.length; i += chunkSize) {\n\t\tconst chunk = s.slice(i, i + chunkSize);\n\t\tresult.push(chunk);\n\t}\n\treturn result;\n}\n\nexport function fillWithEmpty(array: string[], target_length: number) {\n\tconst a = [...array];\n\twhile (a.length < target_length) {\n\t\ta.push(\"\");\n\t}\n\treturn a;\n}\n\nexport function addLineNumbers(\n\ts: string[],\n\tstart_num: number,\n\tprefix = \"~\"\n): string[] {\n\treturn s.map((l, i) => `${prefix}${start_num + i}${l}`);\n}\n\nexport function removeRepeatingSpace(s: string) {\n\treturn s.replace(/ +/g, \" \");\n}\n", "import {\n\taddLineNumbers,\n\tchunks,\n\tfillWithEmpty,\n\tremoveRepeatingSpace,\n} from \"./utils\";\n\nclass Account {\n\tconstructor(\n\t\tpublic number: string,\n\t\tpublic initial_balance: number,\n\t\tpublic closing_balance: number,\n\t\tpublic currency: string\n\t) {}\n}\n\nclass Transaction {\n\tconstructor(\n\t\tpublic acc_date: Date,\n\t\tpublic op_date: Date,\n\t\tpublic description: string,\n\t\tpublic title: string,\n\t\tpublic person: string,\n\t\tpublic account_number: string,\n\t\tpublic amount: number,\n\t\tpublic balance_after: number\n\t) {}\n\n\tpublic static counter = (function* () {\n\t\tlet c = 1;\n\t\twhile (true) {\n\t\t\tyield c.toString().padStart(11, \"0\");\n\t\t\tc++;\n\t\t}\n\t})();\n\n\tformatTitle() {\n\t\treturn addLineNumbers(\n\t\t\tfillWithEmpty(chunks(this.title.trim(), 27), 9),\n\t\t\t20\n\t\t).join(\"\\n\");\n\t}\n\n\tformatPerson() {\n\t\treturn addLineNumbers(\n\t\t\tfillWithEmpty(\n\t\t\t\tchunks(removeRepeatingSpace(this.person).trim(), 27),\n\t\t\t\t2\n\t\t\t).slice(0, 2),\n\t\t\t32\n\t\t).join(\"\\n\");\n\t}\n\n\ttoMT940() {\n\t\tconst prefixes = { incoming: \"150\", outcoming: \"169\" };\n\t\tconst prefix = this.amount > 0 ? prefixes.incoming : prefixes.outcoming;\n\t\treturn `:61:${mtDate(this.acc_date)}${mtDate(this.op_date).slice(\n\t\t\t2\n\t\t)}C${mtAmount(this.amount)}S${prefix}${Transaction.counter.next().value}\n:86:${prefix}\n:86:${prefix}~00B${prefix}${this.description.slice(0, 23)}\n${this.formatTitle()}\n~29${this.account_number}\n~30${this.account_number.slice(2, 10)}\n~31${this.account_number.slice(10)}\n${this.formatPerson()}\n~34${prefix}\n~38PL${this.account_number}\n~62\n~63`;\n\t}\n}\n\nclass Range {\n\tconstructor(public date_start: Date, public date_end: Date) {}\n}\n\nabstract class CSVParser {\n\tabstract read_csv(content_utf8: string): {\n\t\taccount: Account;\n\t\trange: Range;\n\t\ttransactions: Transaction[];\n\t};\n}\n\nclass mBankParser extends CSVParser {\n\tread_csv(content_utf8: string): {\n\t\taccount: Account;\n\t\trange: Range;\n\t\ttransactions: Transaction[];\n\t} {\n\t\tconst lines = content_utf8.split(\"\\r\\n\").map((line) => line.split(\";\"));\n\t\tlet last = 0;\n\t\twhile (!lines[last][0].startsWith(\"Niniejszy dokument sporz\u0105dzono\")) {\n\t\t\tlast++;\n\t\t}\n\t\tconst account = new Account(\n\t\t\tlines[20][0],\n\t\t\tthis.parseAmount(lines[35][1]),\n\t\t\tthis.parseAmount(lines[last - 2][7]),\n\t\t\tlines[18][0]\n\t\t);\n\t\tconst range = new Range(\n\t\t\tthis.parseDate(lines[14][0]),\n\t\t\tthis.parseDate(lines[14][1])\n\t\t);\n\t\tconst transactions = [];\n\t\tfor (let i = 38; i <= last - 5; i++) {\n\t\t\tconst line = lines[i];\n\t\t\tif (line.length != 9) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Wrong amount of columns! maybe a semicolon got stuck in a transaction description?\"\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst date_acc = new Date(line[0]);\n\t\t\tconst date_op = new Date(line[1]);\n\t\t\tconst [description, title, person, account_number] = line\n\t\t\t\t.slice(2)\n\t\t\t\t.map(this.trimString);\n\t\t\tconst [amount, balance_after] = line\n\t\t\t\t.slice(6)\n\t\t\t\t.map((s) => this.parseAmount(s));\n\t\t\ttransactions.push(\n\t\t\t\tnew Transaction(\n\t\t\t\t\tdate_acc,\n\t\t\t\t\tdate_op,\n\t\t\t\t\tdescription,\n\t\t\t\t\ttitle,\n\t\t\t\t\tperson,\n\t\t\t\t\taccount_number,\n\t\t\t\t\tamount,\n\t\t\t\t\tbalance_after\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\treturn { account, range, transactions };\n\t}\n\n\tparseAmount(s: string): number {\n\t\treturn parseFloat(s.replace(/[^0-9,-]/g, \"\").replace(\",\", \".\"));\n\t}\n\n\tparseDate(s: string): Date {\n\t\tconst [day, month, year] = s.split(\".\").map((s) => parseInt(s));\n\t\tconst d = new Date();\n\t\td.setHours(0);\n\t\td.setMinutes(0);\n\t\td.setSeconds(0);\n\t\td.setMilliseconds(0);\n\t\td.setDate(day);\n\t\td.setMonth(month - 1);\n\t\td.setFullYear(year);\n\t\treturn d;\n\t}\n\n\ttrimString(s: string): string {\n\t\t//removes redundant quotes at the beginning or end of the transaction\n\t\treturn s.replaceAll(/(^[\"']|[\"']$)/g, \"\");\n\t}\n}\n\nfunction mtDate(d: Date) {\n\treturn `${d.getFullYear() - 2000}${(d.getMonth() + 1)\n\t\t.toString()\n\t\t.padStart(2, \"0\")}${d.getDate().toString().padStart(2, \"0\")}`;\n}\n\nfunction mtAmount(n: number) {\n\treturn Math.abs(n).toString().replace(\".\", \",\");\n}\n\nexport function convert(csv_utf8: string): { output: string; range: Range } {\n\tconst result = new mBankParser().read_csv(csv_utf8);\n\tconst { account, transactions, range } = result;\n\tconst string = `:20:MT940\n:25:/PL${account.number.replaceAll(/[^0-9]/g, \"\")}\n:28C:${mtDate(range.date_start)}\n:60F:D${mtDate(range.date_start)}${account.currency}${mtAmount(\n\t\taccount.initial_balance\n\t)}\n${transactions.map((t) => t.toMT940()).join(\"\\n\")}\n:62F:D${mtDate(range.date_end)}${account.currency}${mtAmount(\n\t\taccount.closing_balance\n\t)}`;\n\treturn { output: string, range };\n}\n", "import { convert } from \".\";\n\nfunction download(data: string, filename: string, type: string) {\n\tvar file = new Blob([data], { type: type });\n\tvar a = document.createElement(\"a\"),\n\t\turl = URL.createObjectURL(file);\n\ta.href = url;\n\ta.download = filename;\n\tdocument.body.appendChild(a);\n\ta.click();\n\tsetTimeout(function () {\n\t\tdocument.body.removeChild(a);\n\t\twindow.URL.revokeObjectURL(url);\n\t}, 0);\n}\n\nasync function handle() {\n\tconsole.log(\"browser.ts:2\");\n\tconst csv_input = document.getElementById(\"csv\");\n\tif (!csv_input || !(csv_input instanceof HTMLInputElement)) {\n\t\tconsole.log(\"browser.ts:5\");\n\t\tthrow new Error(\"coudl not find the csv input\");\n\t}\n\tvar fileName = csv_input.value.replace(\"C:\\\\fakepath\\\\\", \"\");\n\tconst files = csv_input.files;\n\tif (!files) {\n\t\tthrow new Error(\"no files in the input\");\n\t}\n\tconst buffer = await files[0].arrayBuffer();\n\tconst string = new TextDecoder(\"windows-1250\").decode(buffer);\n\tconst result = convert(string);\n\tdownload(\n\t\tresult.output,\n\t\t`raport-${result.range.date_start.getFullYear()}-${(\n\t\t\tresult.range.date_start.getMonth() + 1\n\t\t)\n\t\t\t.toString()\n\t\t\t.padStart(2, \"0\")}.mt940`,\n\t\t\"text\"\n\t);\n}\n\ndocument.querySelector(\"#submit\")?.addEventListener(\"click\", handle);\nconsole.log(\"added handler to\", document.querySelector(\"#submit\"));\n"], + "mappings": "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", + "names": ["chunks", "s", "chunkSize", "result", "i", "chunk", "fillWithEmpty", "array", "target_length", "a", "addLineNumbers", "start_num", "prefix", "l", "removeRepeatingSpace", "Account", "number", "initial_balance", "closing_balance", "currency", "Transaction", "_Transaction", "acc_date", "op_date", "description", "title", "person", "account_number", "amount", "balance_after", "c", "addLineNumbers", "fillWithEmpty", "chunks", "removeRepeatingSpace", "prefixes", "prefix", "mtDate", "mtAmount", "Range", "date_start", "date_end", "CSVParser", "mBankParser", "content_utf8", "lines", "line", "last", "account", "range", "transactions", "i", "date_acc", "date_op", "s", "day", "month", "year", "d", "n", "convert", "csv_utf8", "result", "t", "download", "data", "filename", "type", "file", "a", "url", "handle", "csv_input", "fileName", "files", "buffer", "string", "result", "convert"] +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..043501a --- /dev/null +++ b/public/index.html @@ -0,0 +1,40 @@ + + + + mBank mt940 konwerter + + + +

mBank mt940 konwerter

+ +

+ Za pomocą tego narzędzia za darmo przekonwertujesz plik CSV + wygenerowany przez mbank do formatu mt940. Konwersja odbywa sie w + przeglądarce, Twoje dane nie są nigdzie wysyłane. Dla pewności + możesz na czas konwersji wyłączyć dostęp do Internetu na Twoim + komputerze, zamknąć tę stronę, i włączyć dostęp do Internetu + ponownie. +

+
+ +

+ Aplikacja została wykonana przez + + Fundację „Internet. Czas działać!” + + i jest utrzymywana przez + Sealcode +

+ + + diff --git a/src/browser.ts b/src/browser.ts new file mode 100644 index 0000000..582e19d --- /dev/null +++ b/src/browser.ts @@ -0,0 +1,44 @@ +import { convert } from "."; + +function download(data: string, filename: string, type: string) { + var file = new Blob([data], { type: type }); + var a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(function () { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); +} + +async function handle() { + console.log("browser.ts:2"); + const csv_input = document.getElementById("csv"); + if (!csv_input || !(csv_input instanceof HTMLInputElement)) { + console.log("browser.ts:5"); + throw new Error("coudl not find the csv input"); + } + var fileName = csv_input.value.replace("C:\\fakepath\\", ""); + const files = csv_input.files; + if (!files) { + throw new Error("no files in the input"); + } + const buffer = await files[0].arrayBuffer(); + const string = new TextDecoder("windows-1250").decode(buffer); + const result = convert(string); + download( + result.output, + `raport-${result.range.date_start.getFullYear()}-${( + result.range.date_start.getMonth() + 1 + ) + .toString() + .padStart(2, "0")}.mt940`, + "text" + ); +} + +document.querySelector("#submit")?.addEventListener("click", handle); +console.log("added handler to", document.querySelector("#submit")); diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..dfa1586 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,57 @@ +import { promises as fs } from "fs"; +import iconv from "iconv-lite"; +import yargs from "yargs"; +import { convert } from "."; +const path = process.argv.at(-1); + +if (!path) { + console.error("Podaj ścieżkę do pliku jako argument w CLI"); + process.exit(); +} + +yargs + .scriptName("mbank-mt940") + .usage("$0 [args]") + .command( + "convert", + "onvert from csv to mt940", + (yargs) => { + yargs.option("i", { + alias: "input", + type: "string", + describe: "path to the mbank csv file", + demandOption: false, + }); + yargs.option("o", { + alias: "output", + type: "string", + describe: "where to save the mt940 file", + demandOption: false, + }); + }, + async function (argv) { + let content: Buffer; + const has_path = argv.input && argv.input !== "--"; + if (has_path) { + console.error("Reading from ", argv.input); + content = await fs.readFile(argv.input as string, { + encoding: null, + }); + } else { + console.error("reading CSV from stdin..."); + content = await new Promise((resolve, reject) => { + let chunks: Buffer[] = []; + process.stdin.on("data", (chunk) => { + chunks.push(chunk); + }); + process.stdin.on("end", () => { + const buffer = Buffer.concat(chunks); + resolve(buffer); + }); + }); + } + const result = convert(iconv.decode(content, "cp1250")); + console.log(result); + } + ) + .help().argv; diff --git a/src/example.test.ts b/src/example.test.ts index 771bd44..e69de29 100644 --- a/src/example.test.ts +++ b/src/example.test.ts @@ -1,11 +0,0 @@ -import { Example } from "./index"; -import * as assert from "assert"; - -describe("Example", () => { - describe("example", () => { - it("should equal 'example'", () => { - const example = new Example(); - assert.equal(example.example(), "example"); - }); - }); -}); diff --git a/src/index.ts b/src/index.ts index dbf6a19..35b6d2a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,186 @@ -export class Example { - example(): string { - return "example"; +import { + addLineNumbers, + chunks, + fillWithEmpty, + removeRepeatingSpace, +} from "./utils"; + +class Account { + constructor( + public number: string, + public initial_balance: number, + public closing_balance: number, + public currency: string + ) {} +} + +class Transaction { + constructor( + public acc_date: Date, + public op_date: Date, + public description: string, + public title: string, + public person: string, + public account_number: string, + public amount: number, + public balance_after: number + ) {} + + public static counter = (function* () { + let c = 1; + while (true) { + yield c.toString().padStart(11, "0"); + c++; + } + })(); + + formatTitle() { + return addLineNumbers( + fillWithEmpty(chunks(this.title.trim(), 27), 9), + 20 + ).join("\n"); + } + + formatPerson() { + return addLineNumbers( + fillWithEmpty( + chunks(removeRepeatingSpace(this.person).trim(), 27), + 2 + ).slice(0, 2), + 32 + ).join("\n"); + } + + toMT940() { + const prefixes = { incoming: "150", outcoming: "169" }; + const prefix = this.amount > 0 ? prefixes.incoming : prefixes.outcoming; + return `:61:${mtDate(this.acc_date)}${mtDate(this.op_date).slice( + 2 + )}C${mtAmount(this.amount)}S${prefix}${Transaction.counter.next().value} +:86:${prefix} +:86:${prefix}~00B${prefix}${this.description.slice(0, 23)} +${this.formatTitle()} +~29${this.account_number} +~30${this.account_number.slice(2, 10)} +~31${this.account_number.slice(10)} +${this.formatPerson()} +~34${prefix} +~38PL${this.account_number} +~62 +~63`; } } + +class Range { + constructor(public date_start: Date, public date_end: Date) {} +} + +abstract class CSVParser { + abstract read_csv(content_utf8: string): { + account: Account; + range: Range; + transactions: Transaction[]; + }; +} + +class mBankParser extends CSVParser { + read_csv(content_utf8: string): { + account: Account; + range: Range; + transactions: Transaction[]; + } { + const lines = content_utf8.split("\r\n").map((line) => line.split(";")); + let last = 0; + while (!lines[last][0].startsWith("Niniejszy dokument sporządzono")) { + last++; + } + const account = new Account( + lines[20][0], + this.parseAmount(lines[35][1]), + this.parseAmount(lines[last - 2][7]), + lines[18][0] + ); + const range = new Range( + this.parseDate(lines[14][0]), + this.parseDate(lines[14][1]) + ); + const transactions = []; + for (let i = 38; i <= last - 5; i++) { + const line = lines[i]; + if (line.length != 9) { + throw new Error( + "Wrong amount of columns! maybe a semicolon got stuck in a transaction description?" + ); + } + const date_acc = new Date(line[0]); + const date_op = new Date(line[1]); + const [description, title, person, account_number] = line + .slice(2) + .map(this.trimString); + const [amount, balance_after] = line + .slice(6) + .map((s) => this.parseAmount(s)); + transactions.push( + new Transaction( + date_acc, + date_op, + description, + title, + person, + account_number, + amount, + balance_after + ) + ); + } + return { account, range, transactions }; + } + + parseAmount(s: string): number { + return parseFloat(s.replace(/[^0-9,-]/g, "").replace(",", ".")); + } + + parseDate(s: string): Date { + const [day, month, year] = s.split(".").map((s) => parseInt(s)); + const d = new Date(); + d.setHours(0); + d.setMinutes(0); + d.setSeconds(0); + d.setMilliseconds(0); + d.setDate(day); + d.setMonth(month - 1); + d.setFullYear(year); + return d; + } + + trimString(s: string): string { + //removes redundant quotes at the beginning or end of the transaction + return s.replaceAll(/(^["']|["']$)/g, ""); + } +} + +function mtDate(d: Date) { + return `${d.getFullYear() - 2000}${(d.getMonth() + 1) + .toString() + .padStart(2, "0")}${d.getDate().toString().padStart(2, "0")}`; +} + +function mtAmount(n: number) { + return Math.abs(n).toString().replace(".", ","); +} + +export function convert(csv_utf8: string): { output: string; range: Range } { + const result = new mBankParser().read_csv(csv_utf8); + const { account, transactions, range } = result; + const string = `:20:MT940 +:25:/PL${account.number.replaceAll(/[^0-9]/g, "")} +:28C:${mtDate(range.date_start)} +:60F:D${mtDate(range.date_start)}${account.currency}${mtAmount( + account.initial_balance + )} +${transactions.map((t) => t.toMT940()).join("\n")} +:62F:D${mtDate(range.date_end)}${account.currency}${mtAmount( + account.closing_balance + )}`; + return { output: string, range }; +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..41e3d3b --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,28 @@ +export function chunks(s: string, chunkSize: number): string[] { + const result = []; + for (let i = 0; i < s.length; i += chunkSize) { + const chunk = s.slice(i, i + chunkSize); + result.push(chunk); + } + return result; +} + +export function fillWithEmpty(array: string[], target_length: number) { + const a = [...array]; + while (a.length < target_length) { + a.push(""); + } + return a; +} + +export function addLineNumbers( + s: string[], + start_num: number, + prefix = "~" +): string[] { + return s.map((l, i) => `${prefix}${start_num + i}${l}`); +} + +export function removeRepeatingSpace(s: string) { + return s.replace(/ +/g, " "); +} diff --git a/tsconfig.json b/tsconfig.json index 2c0f439..f9b496f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,10 +5,10 @@ "noImplicitAny": true, "noImplicitThis": true, "strictNullChecks": true, - "target": "ES6", + "target": "ES2020", "declaration": true, "esModuleInterop": true, - "lib": ["ES6", "ESNext"], + "lib": ["ES6", "ESNext", "DOM"], "outDir": "lib", "checkJs": true, "allowJs": true, @@ -17,6 +17,6 @@ "sourceMap": true, "skipLibCheck": true }, - "include": ["src/**/*", ], + "include": ["src/**/*"], "exclude": ["node_modules/**"] }