New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow using of non-word characters in [import] tag #52
Changes from 2 commits
6b7995a
4d052cd
a5ecb5c
8e4a45a
cfd83fb
4c231e9
2fb9165
89bc9ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,25 @@ | |
const path = require("path"); | ||
const Handlebars = require("handlebars"); | ||
const logger = require("winston-color"); | ||
import { defaultKeyValueMap, initOptions, checkMapTypes, convertValue } from "./options.js"; | ||
const common = require("commonmark/lib/common.js"); | ||
import { defaultKeyValueMap, initOptions, checkMapTypes } from "./options.js"; | ||
import { getLang } from "./language-detection"; | ||
import { getMarker, hasMarker, markerSliceCode, removeMarkers } from "./marker"; | ||
import { sliceCode, hasSliceRange, getSliceRange } from "./slicer"; | ||
import { hasTitle } from "./title"; | ||
import { getTemplateContent, readFileFromPath } from "./template"; | ||
const markdownLinkFormatRegExp = /\[([^\]]*?)\]\(([^\)]*?)\)/gm; | ||
const markdownLinkFormatRegExp = /\[((?:[^\]]|\\.)*?)\]\(((?:[^\)]|\\.)*?)\)/gm; | ||
|
||
const keyEx = "\\w+"; | ||
const kvsepEx = "[:=]"; | ||
const spacesEx = "\\s*"; | ||
const quoteEx = "[\"']"; | ||
const valEx = "(?:[^'\"\\\\]|\\\\.)*"; | ||
const argEx = `${quoteEx}${valEx}${quoteEx}|true|false`; | ||
const expressionEx = `(${keyEx})${kvsepEx}${spacesEx}(${argEx})`; | ||
const expressionRegExp = new RegExp(expressionEx, "g"); | ||
|
||
const markerRegExp = /^\s*(([-\w\s]*,?)*)$/; | ||
|
||
/** | ||
* A counter to count how many code are imported. | ||
|
@@ -68,6 +80,47 @@ export function containIncludeCommand(commands = []) { | |
}); | ||
} | ||
|
||
/** | ||
* Parse the given value to the given type. Returns the value if valid, otherwise returns undefined. | ||
* @param {string} value | ||
* @param {string} type "string", "boolean" | ||
* @param {string} key | ||
* @return {boolean|string|undefined} | ||
*/ | ||
export function parseValue(value, type, key) { | ||
switch (type) { | ||
case "string": | ||
value = common.unescapeString(value.substring(1, value.length - 1)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noted. |
||
if (key === "marker" && !markerRegExp.test(value)) { | ||
logger.error( | ||
"include-codeblock: parseVariablesFromLabel: invalid value " + | ||
`\`${value}\` in key \`marker\`` | ||
); | ||
return undefined; | ||
} | ||
return value; | ||
|
||
case "boolean": | ||
if (["true", '"true"', "'true'"].includes(value)) { | ||
return true; | ||
} | ||
|
||
if (["false", '"false"', "'false'"].includes(value)) { | ||
return false; | ||
} | ||
|
||
logger.error( | ||
"include-codeblock: parseVariablesFromLabel: invalid value " + | ||
`\`${value}\` in key \`${key}\`. Expect true or false.` | ||
); | ||
return undefined; | ||
} | ||
logger.error( | ||
`include-codeblock: parseVariablesFromLabel: unknown key type \`${type}\` (see options.js)` | ||
); | ||
return undefined; | ||
} | ||
|
||
/** Parse the command label and return a new key-value object | ||
* @example | ||
* [import,title:"<thetitle>",label:"<thelabel>"](path/to/file.ext) | ||
|
@@ -77,56 +130,29 @@ export function containIncludeCommand(commands = []) { | |
*/ | ||
export function parseVariablesFromLabel(kvMap, label) { | ||
const kv = Object.assign({}, kvMap); | ||
const beginEx = "^.*"; | ||
const endEx = ".*$"; | ||
const sepEx = ",?"; | ||
const kvsepEx = "[:=]"; | ||
const spacesEx = "\\s*"; | ||
const quotesEx = "[\"']"; | ||
|
||
Object.keys(kv).forEach(key => { | ||
let keyEx = "(" + key + ")"; | ||
let valEx = "([-\\w\\s]*)"; | ||
if (key === "marker") { | ||
keyEx = "(import|include)"; | ||
valEx = "(([-\\w\\s]*,?)*)"; | ||
|
||
let match = ""; | ||
while ((match = expressionRegExp.exec(label))) { | ||
let key = match[1]; | ||
if (["include", "import"].includes(key)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Node.js v4 does not support Array#inclues Can you use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be handled by babel instead of by us? I mean, you can use "babel-preset-env", and specify the node version in .babelrc BTW, I was told that the gitbook server used node 5.1.*. But recently the broken gitbook-cli 2.3.1 moved to node LTS (currentlt v6). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No. Babel only transpile syntax by default.
I think that this PR fix a bug - it is a patch version update. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for your clarification! Done. |
||
key = "marker"; | ||
} | ||
// Add value check here | ||
switch (typeof defaultKeyValueMap[key]) { | ||
case "string": | ||
valEx = quotesEx + valEx + quotesEx; | ||
break; | ||
case "boolean": | ||
// no quotes | ||
valEx = quotesEx + "?(true|false)" + quotesEx + "?"; | ||
break; | ||
default: | ||
logger.error( | ||
"include-codeblock: parseVariablesFromLabel: key type `" + | ||
typeof defaultKeyValueMap[key] + | ||
"` unknown (see options.js)" | ||
); | ||
break; | ||
const value = match[2]; | ||
|
||
if (!kv.hasOwnProperty(key)) { | ||
logger.error( | ||
"include-codeblock: parseVariablesFromLabel: unknown key " + | ||
`\`${key}\` (see options.js)` | ||
); | ||
return; | ||
} | ||
// Val type cast to string. | ||
const regStr = | ||
beginEx + | ||
sepEx + | ||
spacesEx + | ||
keyEx + | ||
spacesEx + | ||
kvsepEx + | ||
spacesEx + | ||
valEx + | ||
spacesEx + | ||
sepEx + | ||
endEx; | ||
const reg = new RegExp(regStr); | ||
const res = label.match(reg); | ||
if (res) { | ||
kv[key] = convertValue(res[2], typeof defaultKeyValueMap[key]); | ||
|
||
const parsedValue = parseValue(value, typeof defaultKeyValueMap[key], key); | ||
if (parsedValue !== undefined) { | ||
kv[key] = parsedValue; | ||
} | ||
}); | ||
} | ||
|
||
return Object.freeze(kv); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import { | |
containIncludeCommand, | ||
splitLabelToCommands, | ||
strip, | ||
parseValue, | ||
parseVariablesFromLabel | ||
} from "../src/parser"; | ||
|
||
|
@@ -45,7 +46,45 @@ describe("parse", function() { | |
assert(containIncludeCommand(commands)); | ||
}); | ||
}); | ||
context("parseVariablesFromLabel ", function() { | ||
describe("parseValue", function() { | ||
it("should unescape string parameter", function() { | ||
const result = parseValue( | ||
'"\\!\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\-\\.\\/' + | ||
"\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~" + | ||
'&<>AA"', | ||
"string", | ||
"" | ||
); | ||
assert.equal(result, "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~&<>AA"); | ||
}); | ||
it("should backslash unescape commonmark defined characters only", function() { | ||
const result = parseValue('"\\r\\n\\[\\]"', "string", ""); | ||
assert.equal(result, "\\r\\n[]"); // \r and \n should not be unescaped. | ||
}); | ||
it("should validate markers", function() { | ||
let result = parseValue('" marker0 , marker1 "', "string", "marker"); | ||
assert.equal(result, " marker0 , marker1 "); | ||
|
||
result = parseValue('"~invalid~"', "string", "marker"); | ||
assert.equal(result, undefined); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noted with thanks. |
||
}); | ||
it("should parse boolean values", function() { | ||
let result = parseValue("true", "boolean", ""); | ||
assert.equal(result, true); | ||
result = parseValue('"true"', "boolean", ""); | ||
assert.equal(result, true); | ||
result = parseValue("'true'", "boolean", ""); | ||
assert.equal(result, true); | ||
|
||
result = parseValue("false", "boolean", ""); | ||
assert.equal(result, false); | ||
result = parseValue('"false"', "boolean", ""); | ||
assert.equal(result, false); | ||
result = parseValue("'false'", "boolean", ""); | ||
assert.equal(result, false); | ||
}); | ||
}); | ||
describe("parseVariablesFromLabel ", function() { | ||
it("should retrieve edit boolean", function() { | ||
const resmap = parseVariablesFromLabel(kvmap, "include,edit:true"); | ||
const results = resmap; | ||
|
@@ -113,6 +152,36 @@ describe("parse", function() { | |
const results = resmap; | ||
assert.equal(results.marker, ""); | ||
}); | ||
it("should handle characters for string parameter", function() { | ||
const resmap = parseVariablesFromLabel( | ||
kvmap, | ||
'import,title="test+with-special*string"' | ||
); | ||
const results = resmap; | ||
assert.equal(results.title, "test+with-special*string"); | ||
}); | ||
it("should unescape string parameter", function() { | ||
const resmap = parseVariablesFromLabel( | ||
kvmap, | ||
'import,title="\\!\\"\\#\\$\\%\\&\\\'\\(\\)\\*\\+\\,\\-\\.\\/' + | ||
"\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~" + | ||
'&<>AA"' | ||
); | ||
const results = resmap; | ||
assert.equal(results.title, "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~&<>AA"); | ||
}); | ||
it("should backslash unescape commonmark defined characters only", function() { | ||
const resmap = parseVariablesFromLabel(kvmap, 'import,title="\\r\\n\\[\\]"'); | ||
const results = resmap; | ||
assert.equal(results.title, "\\r\\n[]"); // \r and \n should not be unescaped. | ||
}); | ||
it("should not parse string argument into another key-value pair", function() { | ||
assert.equal(kvmap.edit, false); | ||
const resmap = parseVariablesFromLabel(kvmap, 'import,title="edit:true"'); | ||
const results = resmap; | ||
assert.equal(results.edit, false); | ||
assert.equal(results.title, "edit:true"); | ||
}); | ||
}); | ||
// inspired from https://github.com/rails/rails/blob/master/activesupport/test/core_ext/string_ext_test.rb | ||
describe("strip", function() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does commonmark export this? ( Is it a undocument API? )
We want to avoid to use direct require.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it is not documented, nor exported by commonmark package.
I'll copy the implementation to our own code (and the license too - which appears to be 2-clause BSD).