diff --git a/lerna.json b/lerna.json index 72b3c6ee01..d5ca80a629 100644 --- a/lerna.json +++ b/lerna.json @@ -10,6 +10,7 @@ "tools/scripts/build", "tools/scripts/formatter", "tools/scripts/formatter/fcc-md-to-gfm", + "tools/formatter", "tools/challenge-helper-scripts" ], "version": "independent" diff --git a/tools/formatter/package-lock.json b/tools/formatter/package-lock.json new file mode 100644 index 0000000000..6386003bf9 --- /dev/null +++ b/tools/formatter/package-lock.json @@ -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==" + } + } +} diff --git a/tools/formatter/package.json b/tools/formatter/package.json new file mode 100644 index 0000000000..33650908b6 --- /dev/null +++ b/tools/formatter/package.json @@ -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" + } +} diff --git a/tools/formatter/translation-annotation/annotate.js b/tools/formatter/translation-annotation/annotate.js new file mode 100644 index 0000000000..85ae86c275 --- /dev/null +++ b/tools/formatter/translation-annotation/annotate.js @@ -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] + })); +} diff --git a/tools/formatter/translation-annotation/create-challenge-string.js b/tools/formatter/translation-annotation/create-challenge-string.js new file mode 100644 index 0000000000..fbfd85820c --- /dev/null +++ b/tools/formatter/translation-annotation/create-challenge-string.js @@ -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 = ''; +const notranslateEnd = ''; + +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
, 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; diff --git a/tools/formatter/translation-annotation/get-challenge-text.js b/tools/formatter/translation-annotation/get-challenge-text.js new file mode 100644 index 0000000000..f6d2639ce5 --- /dev/null +++ b/tools/formatter/translation-annotation/get-challenge-text.js @@ -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; + }; +} diff --git a/tools/formatter/translation-annotation/plugins/question-to-data.js b/tools/formatter/translation-annotation/plugins/question-to-data.js new file mode 100644 index 0000000000..1245995423 --- /dev/null +++ b/tools/formatter/translation-annotation/plugins/question-to-data.js @@ -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; diff --git a/tools/formatter/translation-annotation/plugins/tests-to-data.js b/tools/formatter/translation-annotation/plugins/tests-to-data.js new file mode 100644 index 0000000000..9d2f89d09c --- /dev/null +++ b/tools/formatter/translation-annotation/plugins/tests-to-data.js @@ -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; diff --git a/tools/formatter/translation-annotation/plugins/text-to-data.js b/tools/formatter/translation-annotation/plugins/text-to-data.js new file mode 100644 index 0000000000..ba5ebf1ee9 --- /dev/null +++ b/tools/formatter/translation-annotation/plugins/text-to-data.js @@ -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;