From 246c2e02af5e4e51d8e7ee4f4e799b96f3d1d63b Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 17 Apr 2026 11:32:44 -0700 Subject: [PATCH] make lsattr more robust --- CHANGELOG | 4 ++- jc/parsers/lsattr.py | 67 ++++++++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6c49e26c..dd0e7492 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,12 +1,14 @@ jc changelog -20260330 v1.25.7 +20260417 v1.25.7 - Add `typeset` and `declare` Bash internal command parser to convert variables simple arrays, and associative arrays along with object metadata - Enhance `pip-show` command parser to add `-f` show files support - Enhance `rsync` and `rsync-s` parsers to add `--stats` or `--info=stats[1-3]` fields - Fix `hashsum` command parser to correctly parse the `mode` indicator - Fix `dir` command parser for incorrect stripping of the `D:` drive letter +- Fix `lsattr` command parser for filenames with spaces (newlines in filenames are + still not supported) - Fix `proc-pid-smaps` proc parser when unknown VmFlags are output - Fix `ifconfig` command parser for incorrect stripping of leading zeros in some hex numbers - Fix `iptables` command parser when Target is blank and verbose output is used diff --git a/jc/parsers/lsattr.py b/jc/parsers/lsattr.py index cb2609e2..227fe713 100644 --- a/jc/parsers/lsattr.py +++ b/jc/parsers/lsattr.py @@ -1,5 +1,7 @@ r"""jc - JSON Convert `lsattr` command output parser +> Note: filenames with newlines are not supported. + Usage (cli): $ lsattr | jc --lsattr @@ -57,6 +59,7 @@ Examples: } ] """ +import re from typing import List, Dict from jc.jc_types import JSONDictType import jc.utils @@ -64,7 +67,7 @@ import jc.utils class info(): """Provides parser metadata (version, author, etc.)""" - version = '1.0' + version = '1.1' description = '`lsattr` command parser' author = 'Mark Rotner' author_email = 'rotner.mr@gmail.com' @@ -76,7 +79,7 @@ class info(): __version__ = info.version -ERROR_PREFIX = "lsattr:" +LINE_RE = re.compile(r'(?P[BZXsuSDiadAcEjItTeCFNPV-]{20}) (?P.*)') # https://github.com/mirror/busybox/blob/2d4a3d9e6c1493a9520b907e07a41aca90cdfd94/e2fsprogs/e2fs_lib.c#L40 # https://github.com/landley/toybox/blob/f1682dc79fd75f64042b5438918fe5a507977e1c/toys/other/lsattr.c#L97 @@ -107,6 +110,28 @@ ATTRIBUTES = { } +def _process(proc_data: List[JSONDictType]) -> List[JSONDictType]: + """ + Final processing to conform to the schema. + + Parameters: + + proc_data: (List of Dictionaries) raw structured data to process + + Returns: + + List of Dictionaries. Structured to conform to the schema. + """ + for item in proc_data: + if 'attributes' in item: + for attribute in item['attributes']: + attribute_key = ATTRIBUTES.get(attribute) + if attribute_key: + item[attribute_key] = True + del item['attributes'] + return proc_data + + def parse( data: str, raw: bool = False, @@ -135,32 +160,18 @@ def parse( return output for line in cleandata: - # -R flag returns the output in the format: - # Folder: - # attributes file_in_folder - if line.endswith(':'): - continue - - # lsattr: Operation not supported .... - if line.startswith(ERROR_PREFIX): - continue - - line_output: Dict = {} - - # attributes file # --------------e----- /etc/passwd - # Use maxsplit=1 to handle filenames with spaces (e.g. "./ok ok ok ok ok") - parts = line.split(maxsplit=1) - if len(parts) != 2: - continue - attributes, file = parts - line_output['file'] = file - for attribute in list(attributes): - attribute_key = ATTRIBUTES.get(attribute) - if attribute_key: - line_output[attribute_key] = True + line_split = re.match(LINE_RE, line) - if line_output: - output.append(line_output) + if line_split: + attributes = line_split['attributes'] + file = line_split['filename'] - return output + line_output: Dict = {} + line_output['file'] = file + line_output['attributes'] = attributes + + if line_output: + output.append(line_output) + + return output if raw else _process(output)