@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.
- package/.github/workflows/push.yml +14 -0
- package/.prettierrc +16 -0
- package/.yarn/releases/yarn-1.22.19.cjs +147529 -0
- package/.yarnrc.yml +1 -0
- package/README.md +98 -0
- package/bin/risloo.js +9 -0
- package/package.json +44 -0
- package/src/Gift.js +29 -0
- package/src/Profile.js +405 -0
- package/src/cli-commands/Executor.js +118 -0
- package/src/cli-commands/ExtractExecutor.js +194 -0
- package/src/cli-commands/GiftExecutor.js +56 -0
- package/src/cli-commands/utilities/BaseOps.js +71 -0
- package/src/cli-commands/utilities/Benchmarker.js +88 -0
- package/src/cli-commands/utilities/CustomErrors.js +14 -0
- package/src/cli-commands/utilities/Response.js +59 -0
- package/src/cli-commands/utilities/STATUSES.js +16 -0
- package/src/cli-commands/utilities/Status.js +9 -0
- package/src/cli.js +125 -0
- package/src/handlebars/helpers/abs.js +8 -0
- package/src/handlebars/helpers/addCommas.js +8 -0
- package/src/handlebars/helpers/array.js +8 -0
- package/src/handlebars/helpers/assignGlobal.js +9 -0
- package/src/handlebars/helpers/boolean.js +19 -0
- package/src/handlebars/helpers/ceil.js +8 -0
- package/src/handlebars/helpers/concat.js +8 -0
- package/src/handlebars/helpers/cos.js +8 -0
- package/src/handlebars/helpers/displacePoint.js +11 -0
- package/src/handlebars/helpers/ellipsisChars.js +10 -0
- package/src/handlebars/helpers/ellipsisLines.js +11 -0
- package/src/handlebars/helpers/first.js +11 -0
- package/src/handlebars/helpers/floor.js +8 -0
- package/src/handlebars/helpers/forLoop.js +15 -0
- package/src/handlebars/helpers/getArrOfProp.js +8 -0
- package/src/handlebars/helpers/isArray.js +8 -0
- package/src/handlebars/helpers/join.js +10 -0
- package/src/handlebars/helpers/last.js +11 -0
- package/src/handlebars/helpers/lineWrap.js +28 -0
- package/src/handlebars/helpers/math.js +24 -0
- package/src/handlebars/helpers/modulo.js +8 -0
- package/src/handlebars/helpers/normalizeAngle.js +7 -0
- package/src/handlebars/helpers/object.js +8 -0
- package/src/handlebars/helpers/objectEntries.js +8 -0
- package/src/handlebars/helpers/polarToCartesian.js +14 -0
- package/src/handlebars/helpers/prepend.js +10 -0
- package/src/handlebars/helpers/profiles/bar.js +45 -0
- package/src/handlebars/helpers/profiles/calcGaugeSidePoints.js +34 -0
- package/src/handlebars/helpers/profiles/gauge.js +61 -0
- package/src/handlebars/helpers/profiles/polygon.js +40 -0
- package/src/handlebars/helpers/reverse.js +9 -0
- package/src/handlebars/helpers/roundToTwo.js +8 -0
- package/src/handlebars/helpers/setVar.js +8 -0
- package/src/handlebars/helpers/sin.js +8 -0
- package/src/handlebars/helpers/split.js +8 -0
- package/src/handlebars/helpers/tan.js +8 -0
- package/src/handlebars/helpers/ternary.js +9 -0
- package/src/handlebars/helpers/toDeg.js +8 -0
- package/src/handlebars/helpers/toRad.js +8 -0
- package/src/handlebars/helpers/withGroup.js +20 -0
- package/src/handlebars/helpers/wrapOnNewline.js +11 -0
- package/src/handlebars/helpers.js +86 -0
- package/src/handlebars/importPartials.js +40 -0
- package/src/handlebars/init.js +15 -0
- package/src/publish/bot.js +91 -0
- package/src/publish/json/gift/gift.json +16 -0
- package/src/publish/json/profiles/16PF93.json +201 -0
- package/src/publish/json/profiles/AEQ93.json +96 -0
- package/src/publish/json/profiles/AMS93.json +102 -0
- package/src/publish/json/profiles/AMS9A.json +102 -0
- package/src/publish/json/profiles/BAOMEIS93.json +99 -0
- package/src/publish/json/profiles/BEQI93.json +1589 -0
- package/src/publish/json/profiles/CAATS93.json +11355 -0
- package/src/publish/json/profiles/CADS93.json +351 -0
- package/src/publish/json/profiles/CARSP93.json +96 -0
- package/src/publish/json/profiles/CERQ93.json +106 -0
- package/src/publish/json/profiles/CRAAS93.json +98 -0
- package/src/publish/json/profiles/CSI93.json +143 -0
- package/src/publish/json/profiles/DSWLS93.json +93 -0
- package/src/publish/json/profiles/EMSS93.json +116 -0
- package/src/publish/json/profiles/FACES93.json +106 -0
- package/src/publish/json/profiles/FTEPT93.json +102 -0
- package/src/publish/json/profiles/GMIT93.json +98 -0
- package/src/publish/json/profiles/HPL93.json +102 -0
- package/src/publish/json/profiles/IBT93.json +106 -0
- package/src/publish/json/profiles/IUS93.json +93 -0
- package/src/publish/json/profiles/JCSI93.json +102 -0
- package/src/publish/json/profiles/JPFQ93.json +1 -0
- package/src/publish/json/profiles/KJGI93.json +100 -0
- package/src/publish/json/profiles/LMIQ93.json +109 -0
- package/src/publish/json/profiles/MMFAD93.json +103 -0
- package/src/publish/json/profiles/MMFAD9A.json +103 -0
- package/src/publish/json/profiles/MOCI93.json +100 -0
- package/src/publish/json/profiles/OBQ4493.json +96 -0
- package/src/publish/json/profiles/PIES93.json +104 -0
- package/src/publish/json/profiles/PIES9A.json +104 -0
- package/src/publish/json/profiles/PMCIEF93.json +1 -0
- package/src/publish/json/profiles/PSWQ93.json +97 -0
- package/src/publish/json/profiles/RIASEC93.json +2511 -0
- package/src/publish/json/profiles/SAFE93.json +110 -0
- package/src/publish/json/profiles/SASQ93.json +92 -0
- package/src/publish/json/profiles/SCASP93.json +611 -0
- package/src/publish/json/profiles/SDCAQ93.json +96 -0
- package/src/publish/json/profiles/STAIY93.json +98 -0
- package/src/publish/json/profiles/WAQ93.json +100 -0
- package/src/publish/json/profiles/YBOCS93.json +1322 -0
- package/src/publish/json/profiles/empty.json +139 -0
- package/src/publish/new-version.hbs +11 -0
- package/src/publish/test.js +109 -0
- package/src/qrcode/qrCodeGenerator.js +14 -0
- package/src/qrcode/qrRender.js +229 -0
- package/src/samples/16PF93.js +158 -0
- package/src/samples/AEQ93.js +121 -0
- package/src/samples/AMS93.js +139 -0
- package/src/samples/AMS9A.js +9 -0
- package/src/samples/BAOMEIS93.js +146 -0
- package/src/samples/BEQI93.js +255 -0
- package/src/samples/CAATS93.js +218 -0
- package/src/samples/CADS93.js +161 -0
- package/src/samples/CARSP93.js +134 -0
- package/src/samples/CERQ93.js +195 -0
- package/src/samples/CRAAS93.js +141 -0
- package/src/samples/CSI93.js +315 -0
- package/src/samples/DSWLS93.js +112 -0
- package/src/samples/EMSS93.js +178 -0
- package/src/samples/FACES93.js +225 -0
- package/src/samples/FTEPT93.js +167 -0
- package/src/samples/GMIT93.js +142 -0
- package/src/samples/HPL93.js +132 -0
- package/src/samples/IBT93.js +99 -0
- package/src/samples/IUS93.js +204 -0
- package/src/samples/JCSI93.js +174 -0
- package/src/samples/JPFQ93.js +74 -0
- package/src/samples/KJGI93.js +136 -0
- package/src/samples/LMIQ93.js +188 -0
- package/src/samples/MMFAD93.js +135 -0
- package/src/samples/MMFAD9A.js +31 -0
- package/src/samples/MOCI93.js +128 -0
- package/src/samples/OBQ4493.js +132 -0
- package/src/samples/PIES93.js +228 -0
- package/src/samples/PIES9A.js +32 -0
- package/src/samples/PMCIEF93.js +64 -0
- package/src/samples/PSWQ93.js +126 -0
- package/src/samples/RIASEC93.js +144 -0
- package/src/samples/SAFE93.js +64 -0
- package/src/samples/SASQ93.js +130 -0
- package/src/samples/SCASP93.js +207 -0
- package/src/samples/SDCAQ93.js +174 -0
- package/src/samples/STAIY93.js +159 -0
- package/src/samples/WAQ93.js +208 -0
- package/src/samples/YBOCS93.js +583 -0
- package/src/samples/empty.js +53 -0
- package/views/gift.hbs +160 -0
- package/views/profiles/fonts.css +18 -0
- package/views/profiles/layout.hbs +27 -0
- package/views/profiles/samples/16PF93.hbs +54 -0
- package/views/profiles/samples/AEQ93.hbs +31 -0
- package/views/profiles/samples/AMS93.hbs +97 -0
- package/views/profiles/samples/AMS9A.hbs +1 -0
- package/views/profiles/samples/BAOMEIS93.hbs +48 -0
- package/views/profiles/samples/BEQI93.hbs +118 -0
- package/views/profiles/samples/CAATS93.hbs +251 -0
- package/views/profiles/samples/CADS93.hbs +104 -0
- package/views/profiles/samples/CARSP93.hbs +26 -0
- package/views/profiles/samples/CERQ93.hbs +115 -0
- package/views/profiles/samples/CRAAS93.hbs +88 -0
- package/views/profiles/samples/CSI93_1.hbs +110 -0
- package/views/profiles/samples/CSI93_2.hbs +76 -0
- package/views/profiles/samples/DSWLS93.hbs +26 -0
- package/views/profiles/samples/EMSS93.hbs +101 -0
- package/views/profiles/samples/FACES93_1.hbs +107 -0
- package/views/profiles/samples/FACES93_2.hbs +51 -0
- package/views/profiles/samples/FTEPT93.hbs +67 -0
- package/views/profiles/samples/GMIT93.hbs +45 -0
- package/views/profiles/samples/HPL93.hbs +36 -0
- package/views/profiles/samples/IBT93.hbs +67 -0
- package/views/profiles/samples/IUS93.hbs +123 -0
- package/views/profiles/samples/JCSI93.hbs +65 -0
- package/views/profiles/samples/JPFQ93.hbs +44 -0
- package/views/profiles/samples/KJGI93.hbs +57 -0
- package/views/profiles/samples/LMIQ93.hbs +82 -0
- package/views/profiles/samples/MMFAD93.hbs +35 -0
- package/views/profiles/samples/MMFAD9A.hbs +1 -0
- package/views/profiles/samples/MOCI93.hbs +60 -0
- package/views/profiles/samples/OBQ4493.hbs +61 -0
- package/views/profiles/samples/PIES93.hbs +67 -0
- package/views/profiles/samples/PIES9A.hbs +1 -0
- package/views/profiles/samples/PMCIEF93.hbs +8 -0
- package/views/profiles/samples/PSWQ93.hbs +32 -0
- package/views/profiles/samples/RIASEC93.hbs +38 -0
- package/views/profiles/samples/SAFE93.hbs +118 -0
- package/views/profiles/samples/SASQ93.hbs +45 -0
- package/views/profiles/samples/SCASP93.hbs +94 -0
- package/views/profiles/samples/SDCAQ93.hbs +68 -0
- package/views/profiles/samples/STAIY93.hbs +67 -0
- package/views/profiles/samples/WAQ93.hbs +74 -0
- package/views/profiles/samples/YBOCS93_1.hbs +96 -0
- package/views/profiles/samples/YBOCS93_2.hbs +115 -0
- package/views/profiles/samples/YBOCS93_3.hbs +93 -0
- package/views/profiles/samples/empty.hbs +10 -0
- 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
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;
|