mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-10-31 13:33:06 +00:00
fix: improve parsing of escaped symbols inside string
This commit is contained in:
1 parent
c56a319267
commit
5ad8a2a7d0
3 files changed
+68
-36
No files matched your search
@@ -113,6 +113,13 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
... print(patch.key, patch.value)
|
... print(patch.key, patch.value)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_ESCAPED_CHARACTERS = {
|
||||||
|
"\\$": "$",
|
||||||
|
"\\`": "`",
|
||||||
|
"\\\"": "\"",
|
||||||
|
"\\\n": "",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, stream: IO[bytes]) -> None:
|
def __init__(self, stream: IO[bytes]) -> None:
|
||||||
"""
|
"""
|
||||||
Args:
|
Args:
|
||||||
@@ -121,7 +128,7 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
self._content = stream.read()
|
self._content = stream.read()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _text(node: Node) -> str:
|
def text(node: Node) -> str:
|
||||||
"""
|
"""
|
||||||
read the original source text represented by a syntax tree node
|
read the original source text represented by a syntax tree node
|
||||||
|
|
||||||
@@ -134,7 +141,7 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
return cast(bytes, node.text).decode("utf8", errors="backslashreplace")
|
return cast(bytes, node.text).decode("utf8", errors="backslashreplace")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _unquote(value: str) -> str:
|
def unquote(value: str) -> str:
|
||||||
"""
|
"""
|
||||||
remove shell quotes and escapes from a single value
|
remove shell quotes and escapes from a single value
|
||||||
|
|
||||||
@@ -146,6 +153,37 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
"""
|
"""
|
||||||
return "".join(shlex.split(value, comments=False, posix=True))
|
return "".join(shlex.split(value, comments=False, posix=True))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def value(node: Node) -> str:
|
||||||
|
"""
|
||||||
|
extract a string value from a syntax tree node
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node(Node): syntax tree node to extract
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: extracted string value
|
||||||
|
"""
|
||||||
|
def unescape(source: str) -> str:
|
||||||
|
for original, replacement in PkgbuildParser._ESCAPED_CHARACTERS.items():
|
||||||
|
source = source.replace(original, replacement)
|
||||||
|
return source
|
||||||
|
|
||||||
|
if node.type == PkgbuildNodeType.Concatenation:
|
||||||
|
return "".join(PkgbuildParser.value(child) for child in node.named_children)
|
||||||
|
|
||||||
|
value = PkgbuildParser.text(node)
|
||||||
|
match node.type:
|
||||||
|
case PkgbuildNodeType.String | PkgbuildNodeType.StringContent:
|
||||||
|
if node.type == PkgbuildNodeType.String:
|
||||||
|
value = PkgbuildParser.unquote(value)
|
||||||
|
# protect escaped backslashes from being interpreted a second time.
|
||||||
|
return "\\".join(unescape(part) for part in value.split("\\\\"))
|
||||||
|
case PkgbuildNodeType.Number | PkgbuildNodeType.RawString | PkgbuildNodeType.Word:
|
||||||
|
return PkgbuildParser.unquote(value)
|
||||||
|
case _:
|
||||||
|
return value
|
||||||
|
|
||||||
def _assignment(self, node: Node) -> PkgbuildPatch:
|
def _assignment(self, node: Node) -> PkgbuildPatch:
|
||||||
"""
|
"""
|
||||||
convert an assignment node
|
convert an assignment node
|
||||||
@@ -156,10 +194,8 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
Returns:
|
Returns:
|
||||||
PkgbuildPatch: parsed assignment
|
PkgbuildPatch: parsed assignment
|
||||||
"""
|
"""
|
||||||
name = cast(Node, node.child_by_field_name(PkgbuildNodeField.Name))
|
key = self.text(cast(Node, node.child_by_field_name(PkgbuildNodeField.Name)))
|
||||||
value = cast(Node, node.child_by_field_name(PkgbuildNodeField.Value))
|
value = node.child_by_field_name(PkgbuildNodeField.Value)
|
||||||
key = self._text(name)
|
|
||||||
|
|
||||||
# bash permits assignments without a value (for example ``IFS=``).
|
# bash permits assignments without a value (for example ``IFS=``).
|
||||||
# tree-sitter represents those assignments without a value node.
|
# tree-sitter represents those assignments without a value node.
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -173,7 +209,7 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
return PkgbuildPatch(key, values)
|
return PkgbuildPatch(key, values)
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
return PkgbuildPatch(key, self._value(value))
|
return PkgbuildPatch(key, self.value(value))
|
||||||
|
|
||||||
def _expand_array(self, node: Node) -> list[str]:
|
def _expand_array(self, node: Node) -> list[str]:
|
||||||
"""
|
"""
|
||||||
@@ -186,7 +222,7 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
list[str]: expanded element values
|
list[str]: expanded element values
|
||||||
"""
|
"""
|
||||||
if node.type != PkgbuildNodeType.Concatenation:
|
if node.type != PkgbuildNodeType.Concatenation:
|
||||||
return [self._value(node)]
|
return [self.value(node)]
|
||||||
|
|
||||||
values = [""]
|
values = [""]
|
||||||
children = node.named_children
|
children = node.named_children
|
||||||
@@ -196,15 +232,15 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
Node(type=PkgbuildNodeType.Word, text=b"{"),
|
Node(type=PkgbuildNodeType.Word, text=b"{"),
|
||||||
Node(type=PkgbuildNodeType.Word) as content,
|
Node(type=PkgbuildNodeType.Word) as content,
|
||||||
Node(type=PkgbuildNodeType.Word, text=b"}"),
|
Node(type=PkgbuildNodeType.Word, text=b"}"),
|
||||||
] if "," in (escaped := self._text(content).replace("\\,", ShellInterpolator.DATA_LINK_ESCAPE)):
|
] if "," in (escaped := self.text(content).replace("\\,", ShellInterpolator.DATA_LINK_ESCAPE)):
|
||||||
parts = [
|
parts = [
|
||||||
self._unquote(part).replace(ShellInterpolator.DATA_LINK_ESCAPE, ",")
|
self.unquote(part).replace(ShellInterpolator.DATA_LINK_ESCAPE, ",")
|
||||||
for part in escaped.split(",")
|
for part in escaped.split(",")
|
||||||
]
|
]
|
||||||
children = children[3:]
|
children = children[3:]
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
parts = [self._value(children.pop(0))]
|
parts = [self.value(children.pop(0))]
|
||||||
|
|
||||||
values = [prefix + part for prefix in values for part in parts]
|
values = [prefix + part for prefix in values for part in parts]
|
||||||
|
|
||||||
@@ -242,7 +278,7 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
case PkgbuildNodeType.FunctionDefinition:
|
case PkgbuildNodeType.FunctionDefinition:
|
||||||
name = cast(Node, node.child_by_field_name(PkgbuildNodeField.Name))
|
name = cast(Node, node.child_by_field_name(PkgbuildNodeField.Name))
|
||||||
body = cast(Node, node.child_by_field_name(PkgbuildNodeField.Body))
|
body = cast(Node, node.child_by_field_name(PkgbuildNodeField.Body))
|
||||||
yield PkgbuildPatch(f"{self._text(name)}()", self._text(body))
|
yield PkgbuildPatch(f"{self.text(name)}()", self.text(body))
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
# descend into statements, but skip command arguments and values
|
# descend into statements, but skip command arguments and values
|
||||||
@@ -256,30 +292,6 @@ class PkgbuildParser(Iterable[PkgbuildPatch]):
|
|||||||
):
|
):
|
||||||
yield from self._read(child, values)
|
yield from self._read(child, values)
|
||||||
|
|
||||||
def _value(self, node: Node) -> str:
|
|
||||||
"""
|
|
||||||
extract a string value from a syntax tree node
|
|
||||||
|
|
||||||
Args:
|
|
||||||
node(Node): syntax tree node to extract
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: extracted string value
|
|
||||||
"""
|
|
||||||
if node.type in (PkgbuildNodeType.Concatenation, PkgbuildNodeType.String):
|
|
||||||
return "".join(self._value(child) for child in node.named_children)
|
|
||||||
|
|
||||||
value = self._text(node)
|
|
||||||
match node.type:
|
|
||||||
case PkgbuildNodeType.StringContent:
|
|
||||||
value = f'"{value}"'
|
|
||||||
case PkgbuildNodeType.Number | PkgbuildNodeType.RawString | PkgbuildNodeType.Word:
|
|
||||||
pass
|
|
||||||
case _:
|
|
||||||
return value
|
|
||||||
|
|
||||||
return self._unquote(value)
|
|
||||||
|
|
||||||
def parse(self) -> Iterator[PkgbuildPatch]:
|
def parse(self) -> Iterator[PkgbuildPatch]:
|
||||||
"""
|
"""
|
||||||
parse source and yield extracted entries
|
parse source and yield extracted entries
|
||||||
|
|||||||
@@ -282,4 +282,12 @@ def test_parse(resource_path_root: Path) -> None:
|
|||||||
_ver="$(cat CMakeLists.txt | grep -e SO_MAJOR_VERSION -e SO_MINOR_VERSION -e SO_BUILD_VERSION | cut -d '"' -f2 | head -n3)"
|
_ver="$(cat CMakeLists.txt | grep -e SO_MAJOR_VERSION -e SO_MINOR_VERSION -e SO_BUILD_VERSION | cut -d '"' -f2 | head -n3)"
|
||||||
echo "$(echo ${_ver} | tr ' ' .).r$(git rev-list --count HEAD).$(git rev-parse --short HEAD)"
|
echo "$(echo ${_ver} | tr ' ' .).r$(git rev-list --count HEAD).$(git rev-parse --short HEAD)"
|
||||||
}"""),
|
}"""),
|
||||||
|
PkgbuildPatch("var", "say \"hello\""),
|
||||||
|
PkgbuildPatch("var", "a\\b"),
|
||||||
|
PkgbuildPatch("var", "$name"),
|
||||||
|
PkgbuildPatch("var", "`date`"),
|
||||||
|
PkgbuildPatch("var", "firstsecond"),
|
||||||
|
PkgbuildPatch("var", "a\\qb"),
|
||||||
|
PkgbuildPatch("var", "\\$name"),
|
||||||
|
PkgbuildPatch("var", "first\nsecond"),
|
||||||
]
|
]
|
||||||
@@ -113,3 +113,15 @@ pkgver() {
|
|||||||
_ver="$(cat CMakeLists.txt | grep -e SO_MAJOR_VERSION -e SO_MINOR_VERSION -e SO_BUILD_VERSION | cut -d '"' -f2 | head -n3)"
|
_ver="$(cat CMakeLists.txt | grep -e SO_MAJOR_VERSION -e SO_MINOR_VERSION -e SO_BUILD_VERSION | cut -d '"' -f2 | head -n3)"
|
||||||
echo "$(echo ${_ver} | tr ' ' .).r$(git rev-list --count HEAD).$(git rev-parse --short HEAD)"
|
echo "$(echo ${_ver} | tr ' ' .).r$(git rev-list --count HEAD).$(git rev-parse --short HEAD)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# escapes and newlines inside double-quoted assignments
|
||||||
|
var="say \"hello\""
|
||||||
|
var="a\\b"
|
||||||
|
var="\$name"
|
||||||
|
var="\`date\`"
|
||||||
|
var="first\
|
||||||
|
second"
|
||||||
|
var="a\qb"
|
||||||
|
var="\\\\$name"
|
||||||
|
var="first
|
||||||
|
second"
|
||||||
Reference in new issue
Block a user