feat: add script to annotate challenges for translation (#40366)

* feat: add script to annotate challenges for translation

* fix: readdirp-walk -> readdirp

* fix: remove notranslate for frontmatter

* fix: don't output seed/solution

* feat: convert to function

Also puts in some missing 'async's to make it clearer what returns
promises.

* refactor: use meaningful names

* refactor: remove comments

* chore: update dependencies

* chore: move dir

* fix(crowdin): annotate individual answers
This commit is contained in:
Oliver Eyton-Williams 2020-12-23 20:04:56 +01:00 committed by GitHub
parent 994598900a
commit eb8359c281
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 696 additions and 0 deletions

View File

@ -10,6 +10,7 @@
"tools/scripts/build", "tools/scripts/build",
"tools/scripts/formatter", "tools/scripts/formatter",
"tools/scripts/formatter/fcc-md-to-gfm", "tools/scripts/formatter/fcc-md-to-gfm",
"tools/formatter",
"tools/challenge-helper-scripts" "tools/challenge-helper-scripts"
], ],
"version": "independent" "version": "independent"

393
tools/formatter/package-lock.json generated Normal file
View File

@ -0,0 +1,393 @@
{
"name": "fcc-formatter",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/unist": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
"integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ=="
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"bail": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
"integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ=="
},
"ccount": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz",
"integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw=="
},
"character-entities": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
"integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="
},
"character-entities-html4": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz",
"integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g=="
},
"character-entities-legacy": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
"integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="
},
"character-reference-invalid": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
"integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="
},
"collapse-white-space": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
"integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ=="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"fault": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
"integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
"requires": {
"format": "^0.2.0"
}
},
"format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
"integrity": "sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs="
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"is-alphabetical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
"integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="
},
"is-alphanumeric": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz",
"integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ="
},
"is-alphanumerical": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
"integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
"requires": {
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0"
}
},
"is-buffer": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
"integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
},
"is-decimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
"integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="
},
"is-hexadecimal": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="
},
"is-plain-obj": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
"integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="
},
"is-whitespace-character": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz",
"integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w=="
},
"is-word-character": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz",
"integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA=="
},
"js-yaml": {
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
},
"longest-streak": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz",
"integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg=="
},
"markdown-escapes": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
},
"markdown-table": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz",
"integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==",
"requires": {
"repeat-string": "^1.0.0"
}
},
"mdast-builder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/mdast-builder/-/mdast-builder-1.1.1.tgz",
"integrity": "sha512-a3KBk/LmYD6wKsWi8WJrGU/rXR4yuF4Men0JO0z6dSZCm5FrXXWTRDjqK0vGSqa+1M6p9edeuypZAZAzSehTUw==",
"requires": {
"@types/unist": "^2.0.3"
}
},
"mdast-util-compact": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-2.0.1.tgz",
"integrity": "sha512-7GlnT24gEwDrdAwEHrU4Vv5lLWrEer4KOkAiKT9nYstsTad7Oc1TwqT2zIMKRdZF7cTuaf+GA1E4Kv7jJh8mPA==",
"requires": {
"unist-util-visit": "^2.0.0"
}
},
"parse-entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
"integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
"requires": {
"character-entities": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"character-reference-invalid": "^1.0.0",
"is-alphanumerical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-hexadecimal": "^1.0.0"
}
},
"remark-frontmatter": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-2.0.0.tgz",
"integrity": "sha512-uNOQt4tO14qBFWXenF0MLC4cqo3dv8qiHPGyjCl1rwOT0LomSHpcElbjjVh5CwzElInB38HD8aSRVugKQjeyHA==",
"requires": {
"fault": "^1.0.1"
}
},
"remark-parse": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-8.0.3.tgz",
"integrity": "sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q==",
"requires": {
"ccount": "^1.0.0",
"collapse-white-space": "^1.0.2",
"is-alphabetical": "^1.0.0",
"is-decimal": "^1.0.0",
"is-whitespace-character": "^1.0.0",
"is-word-character": "^1.0.0",
"markdown-escapes": "^1.0.0",
"parse-entities": "^2.0.0",
"repeat-string": "^1.5.4",
"state-toggle": "^1.0.0",
"trim": "0.0.1",
"trim-trailing-lines": "^1.0.0",
"unherit": "^1.0.4",
"unist-util-remove-position": "^2.0.0",
"vfile-location": "^3.0.0",
"xtend": "^4.0.1"
}
},
"remark-stringify": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-8.1.1.tgz",
"integrity": "sha512-q4EyPZT3PcA3Eq7vPpT6bIdokXzFGp9i85igjmhRyXWmPs0Y6/d2FYwUNotKAWyLch7g0ASZJn/KHHcHZQ163A==",
"requires": {
"ccount": "^1.0.0",
"is-alphanumeric": "^1.0.0",
"is-decimal": "^1.0.0",
"is-whitespace-character": "^1.0.0",
"longest-streak": "^2.0.1",
"markdown-escapes": "^1.0.0",
"markdown-table": "^2.0.0",
"mdast-util-compact": "^2.0.0",
"parse-entities": "^2.0.0",
"repeat-string": "^1.5.4",
"state-toggle": "^1.0.0",
"stringify-entities": "^3.0.0",
"unherit": "^1.0.4",
"xtend": "^4.0.1"
}
},
"repeat-string": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
},
"replace-ext": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
"integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"state-toggle": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz",
"integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ=="
},
"stringify-entities": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-3.1.0.tgz",
"integrity": "sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==",
"requires": {
"character-entities-html4": "^1.0.0",
"character-entities-legacy": "^1.0.0",
"xtend": "^4.0.0"
}
},
"to-vfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/to-vfile/-/to-vfile-6.1.0.tgz",
"integrity": "sha512-BxX8EkCxOAZe+D/ToHdDsJcVI4HqQfmw0tCkp31zf3dNP/XWIAjU4CmeuSwsSoOzOTqHPOL0KUzyZqJplkD0Qw==",
"requires": {
"is-buffer": "^2.0.0",
"vfile": "^4.0.0"
}
},
"trim": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz",
"integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0="
},
"trim-trailing-lines": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz",
"integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA=="
},
"trough": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
"integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA=="
},
"unherit": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz",
"integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==",
"requires": {
"inherits": "^2.0.0",
"xtend": "^4.0.0"
}
},
"unified": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/unified/-/unified-9.2.0.tgz",
"integrity": "sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg==",
"requires": {
"bail": "^1.0.0",
"extend": "^3.0.0",
"is-buffer": "^2.0.0",
"is-plain-obj": "^2.0.0",
"trough": "^1.0.0",
"vfile": "^4.0.0"
}
},
"unist-util-is": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.0.2.tgz",
"integrity": "sha512-Ofx8uf6haexJwI1gxWMGg6I/dLnF2yE+KibhD3/diOqY2TinLcqHXCV6OI5gFVn3xQqDH+u0M625pfKwIwgBKQ=="
},
"unist-util-remove-position": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz",
"integrity": "sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA==",
"requires": {
"unist-util-visit": "^2.0.0"
}
},
"unist-util-stringify-position": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
"integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
"requires": {
"@types/unist": "^2.0.2"
}
},
"unist-util-visit": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz",
"integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==",
"requires": {
"@types/unist": "^2.0.0",
"unist-util-is": "^4.0.0",
"unist-util-visit-parents": "^3.0.0"
}
},
"unist-util-visit-parents": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz",
"integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==",
"requires": {
"@types/unist": "^2.0.0",
"unist-util-is": "^4.0.0"
}
},
"vfile": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.0.tgz",
"integrity": "sha512-a/alcwCvtuc8OX92rqqo7PflxiCgXRFjdyoGVuYV+qbgCb0GgZJRvIgCD4+U/Kl1yhaRsaTwksF88xbPyGsgpw==",
"requires": {
"@types/unist": "^2.0.0",
"is-buffer": "^2.0.0",
"replace-ext": "1.0.0",
"unist-util-stringify-position": "^2.0.0",
"vfile-message": "^2.0.0"
}
},
"vfile-location": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-3.1.0.tgz",
"integrity": "sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g=="
},
"vfile-message": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
"integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
"requires": {
"@types/unist": "^2.0.0",
"unist-util-stringify-position": "^2.0.0"
}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
}
}
}

View File

@ -0,0 +1,21 @@
{
"name": "fcc-translation-annotation",
"version": "1.0.0",
"description": "",
"main": "translation-annotation/annotate.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "BSD-3-Clause",
"dependencies": {
"js-yaml": "^3.14.0",
"lodash": "^4.17.20",
"mdast-builder": "^1.1.1",
"remark-frontmatter": "^2.0.0",
"remark-parse": "^8.0.3",
"remark-stringify": "^8.1.1",
"to-vfile": "^6.1.0",
"unified": "^9.2.0"
}
}

View File

@ -0,0 +1,20 @@
const { getText } = require('./get-challenge-text');
const { challengeToString } = require('./create-challenge-string');
const { parseMD } = require('../../challenge-md-parser/mdx');
module.exports.annotate = async function annotate(filePath) {
return generateTranscribableChallenge(filePath)
.then(challengeToString)
.catch(err => {
console.log('Error transforming');
console.log(filePath);
console.log(err);
});
};
async function generateTranscribableChallenge(fullPath) {
return Promise.all([parseMD(fullPath), getText(fullPath)]).then(results => ({
...results[0],
...results[1]
}));
}

View File

@ -0,0 +1,113 @@
const { pick } = require('lodash');
const yaml = require('js-yaml');
const frontmatterProperties = [
'id',
'title',
'challengeType',
'videoId',
'videoUrl',
'forumTopicId',
'isPrivate',
'required',
'helpCategory'
];
const otherProperties = [
'description',
'instructions',
'tests',
'solutions',
'files',
'question'
];
const notranslateStart = '<notranslate>';
const notranslateEnd = '</notranslate>';
function createFrontmatter(data) {
Object.keys(data).forEach(key => {
if (!frontmatterProperties.includes(key) && !otherProperties.includes(key))
throw Error(`Unknown property '${key}'`);
});
// TODO: sort the keys? It doesn't matter from a machine perspective, but
// it does from human-readability one. We could get lucky and have the order
// be preserved accidentally.
const frontData = pick(data, frontmatterProperties);
const frontYAML = yaml.dump(frontData);
return `---
${frontYAML}---
`;
}
// NOTE: trimEnd is used since trailing whitespace is rarely used (it can create
// a <br>, but that's uncommon and hard to read)
function createHints({ tests }) {
if (!tests) return '';
const strTests = tests
.map(
({ text, testString }) => `${text.trimEnd()}
${notranslateStart}
${testString.trimEnd()}
${notranslateEnd}
`
)
.join('\n');
return createSection('hints', strTests);
}
function createQuestion({ question }) {
if (!question) return '';
const { text, answers, solution } = question;
const formattedAnswers = answers.map(
answer => `${notranslateStart}
${answer.trimEnd()}
${notranslateEnd}
`
).join(`
---
`);
const formattedQuestion =
createSection('text', text, { depth: 2 }) +
createSection('answers', formattedAnswers, { depth: 2 }) +
createSection('video-solution', solution, { depth: 2, translate: false });
return createSection('question', formattedQuestion);
}
function createInstructions({ instructions }) {
return createSection('instructions', instructions);
}
function createDescription({ description }) {
return createSection('description', description);
}
function createSection(heading, contents, options) {
const { depth = 1, translate = true } = options || {};
return contents && contents.toString().trim()
? `${notranslateStart}
${''.padEnd(depth, '#')} --${heading}--
${translate ? notranslateEnd + '\n' : ''}
${contents.toString().trimEnd()}
${translate ? '' : notranslateEnd + '\n'}
`
: '';
}
function challengeToString(data) {
const chalString =
createFrontmatter(data) +
createDescription(data) +
createInstructions(data) +
createQuestion(data) +
createHints(data);
// all sections have a trailing '\n', the last one of which needs removing
return chalString.slice(0, -1);
}
exports.challengeToString = challengeToString;

View File

@ -0,0 +1,26 @@
const unified = require('unified');
const vfile = require('to-vfile');
const markdown = require('remark-parse');
const frontmatter = require('remark-frontmatter');
const textToData = require('./plugins/text-to-data');
const testsToData = require('./plugins/tests-to-data');
const questionToData = require('./plugins/question-to-data');
const textProcessor = unified()
.use(markdown)
.use(textToData)
.use(testsToData)
.use(questionToData)
.use(frontmatter, ['yaml']);
exports.getText = createProcessor(textProcessor);
function createProcessor(processor) {
return async msg => {
const file = typeof msg === 'string' ? vfile.readSync(msg) : msg;
const tree = processor.parse(file);
await processor.run(tree, file);
return file.data;
};
}

View File

@ -0,0 +1,64 @@
const { root } = require('mdast-builder');
const getAllBetween = require('../../../challenge-md-parser/mdx/plugins/utils/between-headings');
const {
splitOnThematicBreak
} = require('../../../challenge-md-parser/mdx/plugins/utils/split-on-thematic-break');
const { stringifyMd } = require('./text-to-data');
function plugin() {
return transformer;
function transformer(tree, file) {
const questionNodes = getAllBetween(tree, '--question--');
if (questionNodes.length > 0) {
const questionTree = root(questionNodes);
const textNodes = getAllBetween(questionTree, '--text--');
const answersNodes = getAllBetween(questionTree, '--answers--');
const solutionNodes = getAllBetween(questionTree, '--video-solution--');
const question = getQuestion(textNodes, answersNodes, solutionNodes);
file.data.question = question;
}
}
}
function getQuestion(textNodes, answersNodes, solutionNodes) {
const text = stringifyMd(textNodes);
const answers = getAnswers(answersNodes);
const solution = getSolution(solutionNodes);
if (!text) throw Error('text is missing from question');
if (!answers) throw Error('answers are missing from question');
if (!solution) throw Error('solution is missing from question');
return { text, answers, solution };
}
function getAnswers(answersNodes) {
const answerGroups = splitOnThematicBreak(answersNodes);
return answerGroups.map(answer => stringifyMd(answer));
}
function getSolution(solutionNodes) {
let solution;
try {
if (solutionNodes.length > 1) throw Error('Too many nodes');
if (solutionNodes[0].children.length > 1)
throw Error('Too many child nodes');
const solutionString = solutionNodes[0].children[0].value;
if (solutionString === '') throw Error('Non-empty string required');
solution = Number(solutionString);
if (Number.isNaN(solution)) throw Error('Not a number');
if (solution < 1) throw Error('Not positive number');
} catch (e) {
console.log(e);
throw Error('A video solution should be a positive integer');
}
return solution;
}
module.exports = plugin;

View File

@ -0,0 +1,32 @@
const chunk = require('lodash/chunk');
const getAllBetween = require('../../../challenge-md-parser/mdx/plugins/utils/between-headings');
const { stringifyMd } = require('./text-to-data');
function plugin() {
return transformer;
function transformer(tree, file) {
const hintNodes = getAllBetween(tree, '--hints--');
if (hintNodes.length % 2 !== 0)
throw Error('Tests must be in (text, ```testString```) order');
const tests = chunk(hintNodes, 2).map(getTest);
file.data.tests = tests;
}
}
function getTest(hintNodes) {
const [textNode, testStringNode] = hintNodes;
const text = stringifyMd([textNode]);
const testString = stringifyMd([testStringNode]);
if (!text) throw Error('text is missing from hint');
// stub tests (i.e. text, but no testString) are allowed, but the md must
// have a code block, even if it is empty.
if (!testString && testString !== '')
throw Error('testString (code block) is missing from hint');
return { text, testString };
}
module.exports = plugin;

View File

@ -0,0 +1,26 @@
const stringify = require('remark-stringify');
const { root } = require('mdast-builder');
const unified = require('unified');
const getAllBetween = require('../../../challenge-md-parser/mdx/plugins/utils/between-headings');
const stringifyMd = nodes =>
unified()
.use(stringify, { fences: true, emphasis: '*' })
.stringify(root(nodes));
// NOTE: we need a new plugin (rather than using the challenge parser's plugin)
// simply because it adds html to the descriptions. It's easier to start from
// scratch.
function plugin() {
return transformer;
function transformer(tree, file) {
file.data.description = stringifyMd(getAllBetween(tree, '--description--'));
file.data.instructions = stringifyMd(
getAllBetween(tree, '--instructions--')
);
}
}
module.exports = plugin;
module.exports.stringifyMd = stringifyMd;