@baravak/risloo-profile-cli 3.1.0

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 (200) hide show
  1. package/.github/workflows/push.yml +14 -0
  2. package/.prettierrc +16 -0
  3. package/.yarn/releases/yarn-1.22.19.cjs +147529 -0
  4. package/.yarnrc.yml +1 -0
  5. package/README.md +98 -0
  6. package/bin/risloo.js +9 -0
  7. package/package.json +44 -0
  8. package/src/Gift.js +29 -0
  9. package/src/Profile.js +405 -0
  10. package/src/cli-commands/Executor.js +118 -0
  11. package/src/cli-commands/ExtractExecutor.js +194 -0
  12. package/src/cli-commands/GiftExecutor.js +56 -0
  13. package/src/cli-commands/utilities/BaseOps.js +71 -0
  14. package/src/cli-commands/utilities/Benchmarker.js +88 -0
  15. package/src/cli-commands/utilities/CustomErrors.js +14 -0
  16. package/src/cli-commands/utilities/Response.js +59 -0
  17. package/src/cli-commands/utilities/STATUSES.js +16 -0
  18. package/src/cli-commands/utilities/Status.js +9 -0
  19. package/src/cli.js +125 -0
  20. package/src/handlebars/helpers/abs.js +8 -0
  21. package/src/handlebars/helpers/addCommas.js +8 -0
  22. package/src/handlebars/helpers/array.js +8 -0
  23. package/src/handlebars/helpers/assignGlobal.js +9 -0
  24. package/src/handlebars/helpers/boolean.js +19 -0
  25. package/src/handlebars/helpers/ceil.js +8 -0
  26. package/src/handlebars/helpers/concat.js +8 -0
  27. package/src/handlebars/helpers/cos.js +8 -0
  28. package/src/handlebars/helpers/displacePoint.js +11 -0
  29. package/src/handlebars/helpers/ellipsisChars.js +10 -0
  30. package/src/handlebars/helpers/ellipsisLines.js +11 -0
  31. package/src/handlebars/helpers/first.js +11 -0
  32. package/src/handlebars/helpers/floor.js +8 -0
  33. package/src/handlebars/helpers/forLoop.js +15 -0
  34. package/src/handlebars/helpers/getArrOfProp.js +8 -0
  35. package/src/handlebars/helpers/isArray.js +8 -0
  36. package/src/handlebars/helpers/join.js +10 -0
  37. package/src/handlebars/helpers/last.js +11 -0
  38. package/src/handlebars/helpers/lineWrap.js +28 -0
  39. package/src/handlebars/helpers/math.js +24 -0
  40. package/src/handlebars/helpers/modulo.js +8 -0
  41. package/src/handlebars/helpers/normalizeAngle.js +7 -0
  42. package/src/handlebars/helpers/object.js +8 -0
  43. package/src/handlebars/helpers/objectEntries.js +8 -0
  44. package/src/handlebars/helpers/polarToCartesian.js +14 -0
  45. package/src/handlebars/helpers/prepend.js +10 -0
  46. package/src/handlebars/helpers/profiles/bar.js +45 -0
  47. package/src/handlebars/helpers/profiles/calcGaugeSidePoints.js +34 -0
  48. package/src/handlebars/helpers/profiles/gauge.js +61 -0
  49. package/src/handlebars/helpers/profiles/polygon.js +40 -0
  50. package/src/handlebars/helpers/reverse.js +9 -0
  51. package/src/handlebars/helpers/roundToTwo.js +8 -0
  52. package/src/handlebars/helpers/setVar.js +8 -0
  53. package/src/handlebars/helpers/sin.js +8 -0
  54. package/src/handlebars/helpers/split.js +8 -0
  55. package/src/handlebars/helpers/tan.js +8 -0
  56. package/src/handlebars/helpers/ternary.js +9 -0
  57. package/src/handlebars/helpers/toDeg.js +8 -0
  58. package/src/handlebars/helpers/toRad.js +8 -0
  59. package/src/handlebars/helpers/withGroup.js +20 -0
  60. package/src/handlebars/helpers/wrapOnNewline.js +11 -0
  61. package/src/handlebars/helpers.js +86 -0
  62. package/src/handlebars/importPartials.js +40 -0
  63. package/src/handlebars/init.js +15 -0
  64. package/src/publish/bot.js +91 -0
  65. package/src/publish/json/gift/gift.json +16 -0
  66. package/src/publish/json/profiles/16PF93.json +201 -0
  67. package/src/publish/json/profiles/AEQ93.json +96 -0
  68. package/src/publish/json/profiles/AMS93.json +102 -0
  69. package/src/publish/json/profiles/AMS9A.json +102 -0
  70. package/src/publish/json/profiles/BAOMEIS93.json +99 -0
  71. package/src/publish/json/profiles/BEQI93.json +1589 -0
  72. package/src/publish/json/profiles/CAATS93.json +11355 -0
  73. package/src/publish/json/profiles/CADS93.json +351 -0
  74. package/src/publish/json/profiles/CARSP93.json +96 -0
  75. package/src/publish/json/profiles/CERQ93.json +106 -0
  76. package/src/publish/json/profiles/CRAAS93.json +98 -0
  77. package/src/publish/json/profiles/CSI93.json +143 -0
  78. package/src/publish/json/profiles/DSWLS93.json +93 -0
  79. package/src/publish/json/profiles/EMSS93.json +116 -0
  80. package/src/publish/json/profiles/FACES93.json +106 -0
  81. package/src/publish/json/profiles/FTEPT93.json +102 -0
  82. package/src/publish/json/profiles/GMIT93.json +98 -0
  83. package/src/publish/json/profiles/HPL93.json +102 -0
  84. package/src/publish/json/profiles/IBT93.json +106 -0
  85. package/src/publish/json/profiles/IUS93.json +93 -0
  86. package/src/publish/json/profiles/JCSI93.json +102 -0
  87. package/src/publish/json/profiles/JPFQ93.json +1 -0
  88. package/src/publish/json/profiles/KJGI93.json +100 -0
  89. package/src/publish/json/profiles/LMIQ93.json +109 -0
  90. package/src/publish/json/profiles/MMFAD93.json +103 -0
  91. package/src/publish/json/profiles/MMFAD9A.json +103 -0
  92. package/src/publish/json/profiles/MOCI93.json +100 -0
  93. package/src/publish/json/profiles/OBQ4493.json +96 -0
  94. package/src/publish/json/profiles/PIES93.json +104 -0
  95. package/src/publish/json/profiles/PIES9A.json +104 -0
  96. package/src/publish/json/profiles/PMCIEF93.json +1 -0
  97. package/src/publish/json/profiles/PSWQ93.json +97 -0
  98. package/src/publish/json/profiles/RIASEC93.json +2511 -0
  99. package/src/publish/json/profiles/SAFE93.json +110 -0
  100. package/src/publish/json/profiles/SASQ93.json +92 -0
  101. package/src/publish/json/profiles/SCASP93.json +611 -0
  102. package/src/publish/json/profiles/SDCAQ93.json +96 -0
  103. package/src/publish/json/profiles/STAIY93.json +98 -0
  104. package/src/publish/json/profiles/WAQ93.json +100 -0
  105. package/src/publish/json/profiles/YBOCS93.json +1322 -0
  106. package/src/publish/json/profiles/empty.json +139 -0
  107. package/src/publish/new-version.hbs +11 -0
  108. package/src/publish/test.js +109 -0
  109. package/src/qrcode/qrCodeGenerator.js +14 -0
  110. package/src/qrcode/qrRender.js +229 -0
  111. package/src/samples/16PF93.js +158 -0
  112. package/src/samples/AEQ93.js +121 -0
  113. package/src/samples/AMS93.js +139 -0
  114. package/src/samples/AMS9A.js +9 -0
  115. package/src/samples/BAOMEIS93.js +146 -0
  116. package/src/samples/BEQI93.js +255 -0
  117. package/src/samples/CAATS93.js +218 -0
  118. package/src/samples/CADS93.js +161 -0
  119. package/src/samples/CARSP93.js +134 -0
  120. package/src/samples/CERQ93.js +195 -0
  121. package/src/samples/CRAAS93.js +141 -0
  122. package/src/samples/CSI93.js +315 -0
  123. package/src/samples/DSWLS93.js +112 -0
  124. package/src/samples/EMSS93.js +178 -0
  125. package/src/samples/FACES93.js +225 -0
  126. package/src/samples/FTEPT93.js +167 -0
  127. package/src/samples/GMIT93.js +142 -0
  128. package/src/samples/HPL93.js +132 -0
  129. package/src/samples/IBT93.js +99 -0
  130. package/src/samples/IUS93.js +204 -0
  131. package/src/samples/JCSI93.js +174 -0
  132. package/src/samples/JPFQ93.js +74 -0
  133. package/src/samples/KJGI93.js +136 -0
  134. package/src/samples/LMIQ93.js +188 -0
  135. package/src/samples/MMFAD93.js +135 -0
  136. package/src/samples/MMFAD9A.js +31 -0
  137. package/src/samples/MOCI93.js +128 -0
  138. package/src/samples/OBQ4493.js +132 -0
  139. package/src/samples/PIES93.js +228 -0
  140. package/src/samples/PIES9A.js +32 -0
  141. package/src/samples/PMCIEF93.js +64 -0
  142. package/src/samples/PSWQ93.js +126 -0
  143. package/src/samples/RIASEC93.js +144 -0
  144. package/src/samples/SAFE93.js +64 -0
  145. package/src/samples/SASQ93.js +130 -0
  146. package/src/samples/SCASP93.js +207 -0
  147. package/src/samples/SDCAQ93.js +174 -0
  148. package/src/samples/STAIY93.js +159 -0
  149. package/src/samples/WAQ93.js +208 -0
  150. package/src/samples/YBOCS93.js +583 -0
  151. package/src/samples/empty.js +53 -0
  152. package/views/gift.hbs +160 -0
  153. package/views/profiles/fonts.css +18 -0
  154. package/views/profiles/layout.hbs +27 -0
  155. package/views/profiles/samples/16PF93.hbs +54 -0
  156. package/views/profiles/samples/AEQ93.hbs +31 -0
  157. package/views/profiles/samples/AMS93.hbs +97 -0
  158. package/views/profiles/samples/AMS9A.hbs +1 -0
  159. package/views/profiles/samples/BAOMEIS93.hbs +48 -0
  160. package/views/profiles/samples/BEQI93.hbs +118 -0
  161. package/views/profiles/samples/CAATS93.hbs +251 -0
  162. package/views/profiles/samples/CADS93.hbs +104 -0
  163. package/views/profiles/samples/CARSP93.hbs +26 -0
  164. package/views/profiles/samples/CERQ93.hbs +115 -0
  165. package/views/profiles/samples/CRAAS93.hbs +88 -0
  166. package/views/profiles/samples/CSI93_1.hbs +110 -0
  167. package/views/profiles/samples/CSI93_2.hbs +76 -0
  168. package/views/profiles/samples/DSWLS93.hbs +26 -0
  169. package/views/profiles/samples/EMSS93.hbs +101 -0
  170. package/views/profiles/samples/FACES93_1.hbs +107 -0
  171. package/views/profiles/samples/FACES93_2.hbs +51 -0
  172. package/views/profiles/samples/FTEPT93.hbs +67 -0
  173. package/views/profiles/samples/GMIT93.hbs +45 -0
  174. package/views/profiles/samples/HPL93.hbs +36 -0
  175. package/views/profiles/samples/IBT93.hbs +67 -0
  176. package/views/profiles/samples/IUS93.hbs +123 -0
  177. package/views/profiles/samples/JCSI93.hbs +65 -0
  178. package/views/profiles/samples/JPFQ93.hbs +44 -0
  179. package/views/profiles/samples/KJGI93.hbs +57 -0
  180. package/views/profiles/samples/LMIQ93.hbs +82 -0
  181. package/views/profiles/samples/MMFAD93.hbs +35 -0
  182. package/views/profiles/samples/MMFAD9A.hbs +1 -0
  183. package/views/profiles/samples/MOCI93.hbs +60 -0
  184. package/views/profiles/samples/OBQ4493.hbs +61 -0
  185. package/views/profiles/samples/PIES93.hbs +67 -0
  186. package/views/profiles/samples/PIES9A.hbs +1 -0
  187. package/views/profiles/samples/PMCIEF93.hbs +8 -0
  188. package/views/profiles/samples/PSWQ93.hbs +32 -0
  189. package/views/profiles/samples/RIASEC93.hbs +38 -0
  190. package/views/profiles/samples/SAFE93.hbs +118 -0
  191. package/views/profiles/samples/SASQ93.hbs +45 -0
  192. package/views/profiles/samples/SCASP93.hbs +94 -0
  193. package/views/profiles/samples/SDCAQ93.hbs +68 -0
  194. package/views/profiles/samples/STAIY93.hbs +67 -0
  195. package/views/profiles/samples/WAQ93.hbs +74 -0
  196. package/views/profiles/samples/YBOCS93_1.hbs +96 -0
  197. package/views/profiles/samples/YBOCS93_2.hbs +115 -0
  198. package/views/profiles/samples/YBOCS93_3.hbs +93 -0
  199. package/views/profiles/samples/empty.hbs +10 -0
  200. package/views/profiles/sidebar.hbs +193 -0
package/.yarnrc.yml ADDED
@@ -0,0 +1 @@
1
+ yarnPath: .yarn/releases/yarn-1.22.19.cjs
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Introduction
2
+
3
+ **Risloo Profile CLI** is a library for creating profiles, reports and sheets for *psychological* samples.
4
+
5
+ # Installation
6
+
7
+ In order to install the package, you can write the code below in your terminal:
8
+
9
+ ```
10
+ npm install -g risloo-profile-cli
11
+ ```
12
+
13
+ # How Does It Work?
14
+
15
+ This library is equipped with a CLI in order to create the profiles, reports and sheets you need for your samples.
16
+ The structure of the CLI is as follows:
17
+
18
+ ### Main Structure
19
+
20
+ ```
21
+ risloo [command]
22
+ ```
23
+
24
+ ### Commands
25
+
26
+ ```
27
+ extract|D <sampleName> [sampleOutputs...] [options]
28
+ gift|G [options]
29
+ test|T [options]
30
+ ```
31
+
32
+ ### **extract** Command
33
+
34
+ Using this command, you can draw extract profiles, reports and sheets out of psychological samples.
35
+
36
+ ```
37
+ Arguments:
38
+ sampleName Name of the Sample
39
+ sampleOutputs Outputs To Be Extracted Out of Sample, Choices: {profile, report, sheet}, Default: ["profile"]
40
+
41
+ Options:
42
+ -p, --profile-variant <variant> Variant of the Profile, Choices: {both, raw, with-sidebar}, Default: both
43
+ -i, --input-type <type> Type of Input, Choices: {local, remote, raw-json, stdin}, Default: local
44
+ -d, --input-data <data> Input Data
45
+ -o, --output-type <type> Type of Output, Choices: {local, remote}, Default: local
46
+ -a, --output-address <address> Output Address
47
+ -n, --name <name> Name of Output Profile (Required in "stdin" Input Type)
48
+ -m, --measure Additional Feature for Measuring Dimensions and Distances, Default: false
49
+ -b, --benchmark Time Benchmarking of Command, Default: false
50
+ ```
51
+
52
+ ### **gift** Command
53
+
54
+ Using this command, you can create special gifts for users and customers.
55
+
56
+ ```
57
+ Options:
58
+ -s, --gift-status <status> Gift Status, Choices: {both, open, expired}, Default: both
59
+ -i, --input-type <type> Type of Input, Choices: {raw-json, local}, Default: raw-json
60
+ -d, --input-data <data> Input Data
61
+ -a, --output-address <address> Output Address
62
+ -b, --benchmark Time Benchmarking of Command, Default: false
63
+ ```
64
+
65
+ # CLI Status Codes
66
+
67
+ The **Status Codes** after entering a command is as follows:
68
+
69
+ Profiles Status:
70
+
71
+ | Status Code | Label | Description |
72
+ | ----------- | ----- | ----------- |
73
+ | 0 | Success | Profiles Successfully Created! |
74
+ | 1 | Not Found | Input Data File Does Not Exist! |
75
+ | 2 | Profile JS Error | Error in Instantiating the Profile Object |
76
+ | 3 | Invalid Name | Profile Name Is Not Valid |
77
+ | 4 | Not Found | Profile Template File Does Not Exist |
78
+ | 5 | General Error | Something Went Wrong In The Input Data File! |
79
+ | 6 | General Error | Something Went Wrong In The JS Files! |
80
+
81
+ Gifts Status:
82
+
83
+ | Status Code | Label | Description |
84
+ | ----------- | ----- | ----------- |
85
+ | 0 | Success | Gifts Successfully Created! |
86
+
87
+ # Naming Variables Stratrgy
88
+
89
+ This part is of great importance if you are going to develop and change the source code. We tried much to make code neat and clean, hence commented nearly completely, so that you on the role of developer are able to understand the code rationale as soon as possible!!
90
+
91
+ The *rules* taken into account in the time of creating the variables are:
92
+
93
+ 1. ***items*** is the general term for array of data elements to be drawn inside the profile. An item might contain mark, label, numerical (and maybe non-numerical) values required for drawing (e.g. width, angle, point coordinate, color, opacity and ...).
94
+ 2. If there is the total data element and it has enough differences from other elements (so-called ***items***), we name it ***raw*** which is an object with properties identical to any element of *items*.
95
+ 3. If there is any scale or periodic graduation in the profile, we name it ***ticks*** which is an array including objects with tick number, numerical parameter required for drawing (e.g. coordinate, angle, width, height or ...).
96
+ 4. The ***s*** located at the end of a variable name denotes the array nature of that variable and there is no need for the matter to be emphasized with the suffix ***Arr***.
97
+ 5. Structure for the Dataset Score: `dataset.score = [{label: {eng, ...others}, mark}]`
98
+
package/bin/risloo.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ require("../src/cli")
4
+ .cli(process.argv)
5
+ .catch((err) => {console.error(err)});
6
+
7
+ process.on("unhandledRejection", (reason, promise) => {
8
+ console.log("Unhandled Rejection at:", promise, "reason:", reason);
9
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@baravak/risloo-profile-cli",
3
+ "version": "3.1.0",
4
+ "description": "**Risloo Profile CLI** is a library for creating profiles, reports and sheets for *psychological* samples.",
5
+ "main": "bin/risloo.js",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "bin": {
10
+ "risloo": "bin/risloo.js"
11
+ },
12
+ "scripts": {
13
+ "bot": "node ./src/publish/bot.js",
14
+ "test": "node ./src/publish/test.js",
15
+ "prepublishOnly": "npm test",
16
+ "postpublish": "npm run bot",
17
+ "start": "node app"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/baravak/risloo-extractor-app.git"
22
+ },
23
+ "author": "Mahdi Amirabadi",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "chalk": "^4.1.2",
27
+ "chokidar": "^3.5.3",
28
+ "commander": "^8.3.0",
29
+ "dotenv": "^16.0.0",
30
+ "form-data": "^4.0.0",
31
+ "handlebars": "^4.7.7",
32
+ "moment": "^2.29.1",
33
+ "moment-jalaali": "^0.9.2",
34
+ "prettier": "^2.4.1",
35
+ "qrcode": "^1.5.0",
36
+ "sharp": "^0.30.1"
37
+ },
38
+ "packageManager": "yarn@1.22.19",
39
+ "bugs": {
40
+ "url": "https://github.com/baravak/risloo-extractor-app/issues"
41
+ },
42
+ "homepage": "https://github.com/baravak/risloo-extractor-app#readme",
43
+ "devDependencies": {}
44
+ }
package/src/Gift.js ADDED
@@ -0,0 +1,29 @@
1
+ const moment = require("moment-jalaali");
2
+
3
+ moment.locale("fa");
4
+ moment.loadPersian({ dialect: "persian-modern" });
5
+
6
+ class Gift {
7
+ constructor(gift, avatar) {
8
+ this.name = gift.code;
9
+ this.title = gift.title || "-";
10
+ this.code = gift.code.slice(10) || "-";
11
+ this.type = gift.type || "-";
12
+ this.value = gift.value || "-";
13
+ (this.started_at = (gift.started_at && this._formatDate(gift.started_at)) || "-"),
14
+ (this.expires_at = (gift.expires_at && this._formatDate(gift.expires_at)) || ""),
15
+ (this.region = {
16
+ id: gift.region.id || "-",
17
+ type: gift.region.type,
18
+ title: gift.region.detail.title || "-",
19
+ avatarBase64: avatar && Buffer.from(avatar, "binary").toString("base64"),
20
+ });
21
+ }
22
+
23
+ // Convert Given Timestamp to Proper Format for Gift
24
+ _formatDate(timeStamp) {
25
+ return moment(timeStamp * 1000).format("jDD / jMM / jYYYY");
26
+ }
27
+ }
28
+
29
+ module.exports = Gift;
package/src/Profile.js ADDED
@@ -0,0 +1,405 @@
1
+ const moment = require("moment-jalaali");
2
+ const qrCodeGenerator = require("./qrcode/qrCodeGenerator");
3
+
4
+ moment.locale("fa");
5
+ moment.loadPersian({ dialect: "persian-modern" });
6
+
7
+ // Implement String.prototype.format
8
+ String.prototype.format = function () {
9
+ var args = arguments;
10
+ return this.replace(/{(\d+)}/g, function (match, number) {
11
+ return typeof args[number] != "undefined" ? args[number] : match;
12
+ });
13
+ };
14
+
15
+ class FS {
16
+ static calcDistance(pt1, pt2) {
17
+ return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
18
+ }
19
+
20
+ static toDegrees(rad) {
21
+ return rad * (180 / Math.PI);
22
+ }
23
+
24
+ static toRadians(deg) {
25
+ return deg * (Math.PI / 180);
26
+ }
27
+
28
+ static isOdd(n) {
29
+ return !!(n % 2);
30
+ }
31
+
32
+ // Create a Line Using the Two Points and Return It As A Function
33
+ static createLine(pt1, pt2) {
34
+ let m = (pt2.y - pt1.y) / (pt2.x - pt1.x);
35
+ let intercept = -m * pt1.x + pt1.y;
36
+
37
+ return function (point) {
38
+ return m * point + intercept;
39
+ };
40
+ }
41
+
42
+ // Create Arithmetic Sequence for Angles of Polygon
43
+
44
+ static createArithmeticSequence(a_0, d, n) {
45
+ let i;
46
+ let arr = [];
47
+ for (i = 0; i < n; i++) {
48
+ arr.push(a_0 + i * d);
49
+ }
50
+ return arr;
51
+ }
52
+
53
+ // Round to 2 Decimal Places
54
+ static roundTo2(n) {
55
+ return +n.toFixed(2);
56
+ }
57
+
58
+ // Rotate Axes of 2D Coordinate System for a Point (Counterclockwise)
59
+
60
+ static rotateAxes(pt, theta) {
61
+ return {
62
+ x: FS.roundTo2(pt.x * Math.cos(theta) - pt.y * Math.sin(theta)),
63
+ y: FS.roundTo2(pt.x * Math.sin(theta) + pt.y * Math.cos(theta)),
64
+ };
65
+ }
66
+
67
+ // Translate Axes of 2D Coordinate System for a Point
68
+
69
+ static translateAxes(pt, d) {
70
+ return { x: FS.roundTo2(pt.x - d.x), y: FS.roundTo2(pt.y - d.y) };
71
+ }
72
+
73
+ // Transform Axes of 2D Coordinate System for a Point
74
+
75
+ static transformAxes(pt, d, theta) {
76
+ return FS.rotateAxes(FS.translateAxes(pt, d), theta);
77
+ }
78
+
79
+ // Check Whether an Object is Empty
80
+ static isEmpty(obj) {
81
+ return Object.keys(obj).length === 0;
82
+ }
83
+
84
+ static isObject(item) {
85
+ return item && typeof item === "object" && !Array.isArray(item);
86
+ }
87
+
88
+ static deepMerge(target, source) {
89
+ if (FS.isObject(target) && FS.isObject(source)) {
90
+ for (const key in source) {
91
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
92
+ if (FS.isObject(source[key]) && !(descriptor["get"] || descriptor["set"])) {
93
+ if (!target[key]) Object.assign(target, { [key]: {} });
94
+ FS.deepMerge(target[key], source[key]);
95
+ } else {
96
+ Object.defineProperties(target, {
97
+ [key]: descriptor,
98
+ });
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ // Classes
106
+
107
+ class Canvas {
108
+ constructor(canvas, variant) {
109
+ this.width = canvas.width;
110
+ this.height = canvas.height;
111
+ this._init(canvas, variant);
112
+ }
113
+
114
+ _init(canvas, variant) {
115
+ this.profile = { width: this.width, height: this.height };
116
+ if (variant === "with-sidebar") {
117
+ this.header = canvas["sidebar-variant"].header;
118
+ this.sidebar = canvas["sidebar-variant"].sidebar;
119
+ this.profile = {
120
+ width: this.width - this.sidebar.width,
121
+ height: this.height - this.header.height,
122
+ };
123
+ }
124
+ this.profile["padding"] = canvas.profile.padding;
125
+ }
126
+ }
127
+
128
+ class Dataset {
129
+ // Default Sample Info for the Profile
130
+ defaultSample = {
131
+ questions: false,
132
+ defaultFields: true,
133
+ fields: [],
134
+ };
135
+
136
+ // Default Prerequisites to Be Shown in the Sidebar
137
+ defaultFields = ["gender", "age", "education", "marital_status"];
138
+
139
+ constructor(dataset, spec) {
140
+ // Getting Class Fields
141
+ const { defaultSample, defaultFields } = this;
142
+
143
+ // Destructure Required Info from Spec of the Profile
144
+ const { labels, sample = defaultSample, labelsPrefix } = spec;
145
+
146
+ // Specifying Prerequisites that are Going to Be Extracted
147
+ // *** Remember That You Should Get The Copy of Arrays Taken from Spec of the Profile
148
+ const requiredPreqs = sample.defaultFields ? [...defaultFields, ...sample.fields] : [...sample.fields];
149
+
150
+ this.info = {
151
+ id: dataset.id || "-",
152
+ title: dataset.scale?.title || "-",
153
+ clientName: dataset.client?.name || "-",
154
+ managerName: dataset.room?.manager?.name || "-",
155
+ centerTitle: dataset.room?.center?.detail?.title || "-",
156
+ started_at: (dataset.started_at && this._formatDate(dataset.started_at)) || "-",
157
+ closed_at: (dataset.closed_at && this._formatDate(dataset.closed_at)) || "-",
158
+ scored_at: (dataset.scored_at && this._formatDate(dataset.scored_at)) || "-",
159
+ time: this._formatTime(dataset.cornometer) || "-",
160
+ fields: dataset.prerequisites ? this._extractFields(dataset.prerequisites, requiredPreqs) : dataset.fields,
161
+ };
162
+
163
+ // Extract Questions if sample.questions === true
164
+ // *** Remember That You Should Get The Copy of Arrays Taken from Dataset
165
+ this.questions = sample.questions && [...dataset.items];
166
+
167
+ this.score = this._extractData(dataset.score, labels, labelsPrefix);
168
+ }
169
+
170
+ // Convert Given Timestamp to Proper Format for Profile
171
+ _formatDate(timeStamp) {
172
+ return moment(timeStamp * 1000).format("jYYYY.jMM.jDD - HH:mm");
173
+ }
174
+
175
+ // Convert Given Time to Proper Format for Profile
176
+ _formatTime(sec) {
177
+ const time = {
178
+ hour: Math.floor(sec / 60),
179
+ minute: sec % 60,
180
+ };
181
+
182
+ return (
183
+ (time.hour ? `${time.hour} ساعت` : "") +
184
+ (time.hour && time.minute ? " و " : "") +
185
+ (time.minute ? `${time.minute} دقیقه` : "")
186
+ );
187
+ }
188
+
189
+ // Extract Fields Using the Prerequisities Array
190
+ _extractFields(preqs, requiredPreqs) {
191
+ // "Fields" is the Output of the Method
192
+ const fields = [];
193
+
194
+ let field;
195
+
196
+ requiredPreqs.forEach((reqPreq) => {
197
+ if ((FS.isObject(reqPreq) && reqPreq["eng"]) || !FS.isObject(reqPreq)) field = this._extractField(preqs, reqPreq);
198
+ else field = this._mergeFields(preqs, reqPreq);
199
+ fields.push(field);
200
+ });
201
+
202
+ return fields;
203
+ }
204
+
205
+ _extractField(preqs, reqPreq) {
206
+ let engLabel = reqPreq.eng || reqPreq;
207
+ let preq = preqs?.find((item) => item.label === engLabel);
208
+ return { eng: engLabel, fr: reqPreq.fr || preq?.text || "-", value: this._extractValue(preq) };
209
+ }
210
+
211
+ _mergeFields(preqs, reqPreq) {
212
+ let values = reqPreq["merge"].map((label) => {
213
+ let preq = preqs.find((item) => item.label === label);
214
+ return this._extractValue(preq);
215
+ });
216
+ return { eng: reqPreq["merge"].join("+"), fr: reqPreq.fr, value: reqPreq.valueFormat.format(...values) };
217
+ }
218
+
219
+ _extractValue(preq) {
220
+ let val;
221
+
222
+ if (preq?.answer.type !== "select") val = preq?.user_answered || "-";
223
+ else val = preq?.answer.options[preq.user_answered - 1] || "-";
224
+
225
+ return val;
226
+ }
227
+
228
+ // Create Data Array with Label and Mark Taken from Dataset
229
+ _extractData(score, labels, labelsPrefix) {
230
+ return labels.map((label) => ({
231
+ label,
232
+ mark: score[label.eng] || score[`${labelsPrefix}_${label.eng}`],
233
+ }));
234
+ }
235
+
236
+ // Group By Special Property of the Label of the Dataset Score
237
+ groupBy(prop) {
238
+ const { score } = this;
239
+
240
+ const scoreGroups = score.reduce((groups, data, index) => {
241
+ const group = groups.find((group) => group[0].label[prop] === data.label[prop] && data.label[prop] !== "-");
242
+ if (group) group.push(data);
243
+ else groups.push([data]);
244
+ return groups;
245
+ }, []);
246
+
247
+ return scoreGroups;
248
+ }
249
+ }
250
+
251
+ class Mappings extends Array {
252
+ addMapping(range, value) {
253
+ this.push({ range, value });
254
+ return this;
255
+ }
256
+
257
+ map(val) {
258
+ const me = this;
259
+ for (let mapping of me) {
260
+ const splitted = mapping.range.split("-");
261
+ if (splitted.length === 1) {
262
+ if (+splitted[0] === val) return mapping.value;
263
+ } else if (+splitted[0] <= val && val <= +splitted[1]) {
264
+ return mapping.value;
265
+ }
266
+ }
267
+ }
268
+ }
269
+
270
+ class Ticks {
271
+ constructor(min, max, n) {
272
+ this.numbers = this._calcNumbers(min, max, n);
273
+ }
274
+
275
+ _calcNumbers(min, max, n) {
276
+ let output = [min];
277
+ const commonDiff = FS.roundTo2((max - min) / (n - 1));
278
+ output.push(...FS.createArithmeticSequence(min + commonDiff, commonDiff, n - 2), max);
279
+ return output;
280
+ }
281
+ }
282
+
283
+ class Spec {
284
+ constructor(config, profileSpec) {
285
+ this.parameters = {
286
+ canvas: {
287
+ width: 1123,
288
+ height: 794,
289
+ profile: {
290
+ padding: 20,
291
+ },
292
+ "sidebar-variant": {
293
+ header: {
294
+ height: 40,
295
+ paddingX: 20,
296
+ },
297
+ sidebar: {
298
+ width: 180,
299
+ height: 754,
300
+ iconPadding: 10,
301
+ padding: {
302
+ x: 20,
303
+ y: 20,
304
+ },
305
+ lineHeight: {
306
+ low: 19,
307
+ medium: 20,
308
+ high: 21,
309
+ },
310
+ icons: {
311
+ offsetY: 27,
312
+ paddingX: 7,
313
+ person: {
314
+ width: 14.22,
315
+ ratio: 16 / 18,
316
+ },
317
+ calender: {
318
+ width: 14.17,
319
+ ratio: 15 / 18,
320
+ },
321
+ clock: {
322
+ width: 14.53,
323
+ ratio: 16 / 16,
324
+ },
325
+ },
326
+ fields: {
327
+ lineHeight: 20,
328
+ offsetY: 46,
329
+ paddingX: 11,
330
+ },
331
+ qrcode: {
332
+ width: 80,
333
+ get height() {
334
+ return this.width;
335
+ },
336
+ },
337
+ },
338
+ },
339
+ },
340
+ ...profileSpec,
341
+ };
342
+ this._setConfig(config);
343
+ }
344
+
345
+ _setConfig(config) {
346
+ const { parameters } = this;
347
+
348
+ FS.deepMerge(parameters, config);
349
+ }
350
+ }
351
+
352
+ class Profile {
353
+ constructor() {
354
+ if (this.constructor.name === "Profile") throw new Error("Can't Instantiate Abstract Class");
355
+ }
356
+
357
+ _init(dataset, options, config) {
358
+ const { variant, measure } = options;
359
+ this.variant = variant;
360
+ this.measure = measure;
361
+
362
+ this.spec = new Spec(config, this.profileSpec);
363
+ const { canvas } = this.spec.parameters;
364
+ this.canvas = new Canvas(canvas, variant);
365
+
366
+ this.dataset = new Dataset(dataset, this.spec.parameters);
367
+
368
+ if (this.variant === "with-sidebar") this._generateQRCode();
369
+
370
+ this.contextArr = this._calcContext();
371
+ }
372
+
373
+ _generateQRCode() {
374
+ const { dataset, canvas } = this;
375
+ const width = canvas.sidebar?.qrcode?.width || 120;
376
+ const height = canvas.sidebar?.qrcode?.height || 120;
377
+ const data = `https://r1l.ir/${dataset.info.id}/?utm_source=risloo.ir&utm_medium=profile&utm_campaign=${dataset.info.id}`;
378
+ this.qrcode = { link: data, svg: qrCodeGenerator(data, { width, height }) };
379
+ }
380
+
381
+ getTemplateEngineParams() {
382
+ const {
383
+ variant,
384
+ measure,
385
+ canvas,
386
+ dataset,
387
+ spec: { parameters: spec },
388
+ qrcode,
389
+ contextArr,
390
+ } = this;
391
+
392
+ return contextArr.map((context, index) => ({
393
+ page: index + 1,
394
+ canvas,
395
+ dataset,
396
+ spec,
397
+ qrcode,
398
+ ...context,
399
+ variant,
400
+ measure,
401
+ }));
402
+ }
403
+ }
404
+
405
+ module.exports = { Profile, Dataset, FS, Ticks, Mappings };
@@ -0,0 +1,118 @@
1
+ const { checkAndLoad, loadStdin, ensureDirExistence, createSVG, createPNG } = require("./utilities/BaseOps");
2
+ const Handlebars = require("../handlebars/init");
3
+ const path = require("path");
4
+ const Benchmarker = require("./utilities/Benchmarker");
5
+ const Response = require("./utilities/Response");
6
+
7
+ class Executor {
8
+ constructor(options) {
9
+ this.command = options.command;
10
+ this.input = {
11
+ type: options.inputType,
12
+ data: options.inputData,
13
+ };
14
+ this.output = {
15
+ type: options.outputType,
16
+ address: options.outputAddress,
17
+ };
18
+ this.response = new Response();
19
+ if (options.benchmark) this._initBenchmarker();
20
+ this._initPromises();
21
+ }
22
+
23
+ _initBenchmarker() {
24
+ const { command: commandName } = this;
25
+
26
+ this.benchmarker = new Benchmarker();
27
+ this.benchmarker.start(`${commandName} Command`);
28
+ }
29
+
30
+ _initPromises() {
31
+ this.promises = {};
32
+
33
+ this._addInputPromise();
34
+ this._addOutputPromise();
35
+ }
36
+
37
+ _addInputPromise() {
38
+ const {
39
+ input: { type, data },
40
+ } = this;
41
+
42
+ let jsonPromise;
43
+
44
+ switch (type) {
45
+ case "local":
46
+ if (!data) throw new Error("No Input Data Provided");
47
+ jsonPromise = checkAndLoad(data);
48
+ break;
49
+ case "stdin":
50
+ jsonPromise = loadStdin("utf-8");
51
+ break;
52
+ case "raw-json":
53
+ if (!data) throw new Error("No Input Data Provided");
54
+ jsonPromise = Promise.resolve(data);
55
+ break;
56
+ case "remote":
57
+ break;
58
+ }
59
+
60
+ this.promises["input"] = jsonPromise.then((json) => Promise.resolve(JSON.parse(json)));
61
+ }
62
+
63
+ _addOutputPromise() {
64
+ const {
65
+ output: { type, address },
66
+ } = this;
67
+
68
+ switch (type) {
69
+ case "local":
70
+ this.promises["output"] = ensureDirExistence(address);
71
+ break;
72
+ case "raw-json":
73
+ break;
74
+ }
75
+ }
76
+
77
+ async _renderTemplate(ctx, templateBuffer) {
78
+ const template = (await Handlebars).compile(templateBuffer.toString(), "utf-8");
79
+ return template(ctx);
80
+ }
81
+
82
+ async _createOutput(xml, { address, fileName }, ext) {
83
+ const dir = path.join(address, `${fileName}.${ext}`);
84
+ return ext === "svg" ? createSVG(xml, dir) : ext === "png" ? createPNG(xml, dir) : Promise.resolve();
85
+ }
86
+
87
+ async _renderAndCreateOutput(ctx, templatePromise, fileName, extensions) {
88
+ const {
89
+ promises,
90
+ output: { address },
91
+ } = this;
92
+ let xml;
93
+
94
+ return templatePromise
95
+ .then(async (templateBuffer) => {
96
+ xml = await this._renderTemplate(ctx, templateBuffer);
97
+ return promises.output;
98
+ })
99
+ .then(() => Promise.all(extensions.map((ext) => this._createOutput(xml, { address, fileName }, ext))));
100
+ }
101
+
102
+ async _renderAndCreateOutputs(contexts, templatePromises, outputFileName, extensions) {
103
+ return Promise.all(
104
+ templatePromises.map((templatePromise, index) => {
105
+ let fileName = `${outputFileName}${index !== 0 ? ".page" + (index + 1) : ""}`;
106
+ return this._renderAndCreateOutput(contexts[index], templatePromise, fileName, extensions);
107
+ })
108
+ );
109
+ }
110
+
111
+ getFinalPromise() {
112
+ const { promises, response } = this;
113
+
114
+ return Promise.all(Object.values(promises)).finally(() => response.showOutput());
115
+ }
116
+ }
117
+
118
+ module.exports = Executor;