From 1c0a35dff8fa576346530c25359c869b500e9882 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 27 Feb 2026 11:37:58 -0800 Subject: [PATCH 01/10] version bump --- CHANGELOG | 4 +++- jc/lib.py | 2 +- setup.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d911bb90..67782fa5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ jc changelog -202501012 v1.25.6 +20260227 v1.25.7 + +20251012 v1.25.6 - Add `net-localgroup` Windows command parser - Add `net-user` Windows command parser - Add `route-print` Windows command parser diff --git a/jc/lib.py b/jc/lib.py index c21a0284..7d1ea9d3 100644 --- a/jc/lib.py +++ b/jc/lib.py @@ -10,7 +10,7 @@ from jc import appdirs from jc import utils -__version__ = '1.25.6' +__version__ = '1.25.7' parsers: List[str] = [ 'acpi', diff --git a/setup.py b/setup.py index 6b6b6dce..71a891a3 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open('README.md', 'r') as f: setuptools.setup( name='jc', - version='1.25.6', + version='1.25.7', author='Kelly Brazil', author_email='kellyjonbrazil@gmail.com', description='Converts the output of popular command-line tools and file-types to JSON.', From f3352352ed7ad229a1845e6d60bd9115e2273421 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 27 Feb 2026 12:08:08 -0800 Subject: [PATCH 02/10] fix unknown flags throwing key error (#681) --- jc/parsers/proc_pid_smaps.py | 71 +++++++++++-------- .../linux-proc/pid_smaps_unknown_flag | 46 ++++++++++++ .../linux-proc/pid_smaps_unknown_flag.json | 1 + tests/test_proc_pid_smaps.py | 12 +++- 4 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 tests/fixtures/linux-proc/pid_smaps_unknown_flag create mode 100644 tests/fixtures/linux-proc/pid_smaps_unknown_flag.json diff --git a/jc/parsers/proc_pid_smaps.py b/jc/parsers/proc_pid_smaps.py index d7be98fc..bedd6a08 100644 --- a/jc/parsers/proc_pid_smaps.py +++ b/jc/parsers/proc_pid_smaps.py @@ -168,7 +168,7 @@ import jc.utils class info(): """Provides parser metadata (version, author, etc.)""" - version = '1.0' + version = '1.1' description = '`/proc//smaps` file parser' author = 'Kelly Brazil' author_email = 'kellyjonbrazil@gmail.com' @@ -205,33 +205,46 @@ def _process(proc_data: List[Dict]) -> List[Dict]: vmflags_map = { 'rd': 'readable', - 'wr': 'writeable', - 'ex': 'executable', - 'sh': 'shared', - 'mr': 'may read', - 'mw': 'may write', - 'me': 'may execute', - 'ms': 'may share', - 'mp': 'MPX-specific VMA', - 'gd': 'stack segment growns down', - 'pf': 'pure PFN range', - 'dw': 'disabled write to the mapped file', - 'lo': 'pages are locked in memory', - 'io': 'memory mapped I/O area', - 'sr': 'sequential read advise provided', - 'rr': 'random read advise provided', - 'dc': 'do not copy area on fork', - 'de': 'do not expand area on remapping', - 'ac': 'area is accountable', - 'nr': 'swap space is not reserved for the area', - 'ht': 'area uses huge tlb pages', - 'ar': 'architecture specific flag', - 'dd': 'do not include area into core dump', - 'sd': 'soft-dirty flag', - 'mm': 'mixed map area', - 'hg': 'huge page advise flag', - 'nh': 'no-huge page advise flag', - 'mg': 'mergable advise flag' + 'wr': 'writeable', + 'ex': 'executable', + 'sh': 'shared', + 'mr': 'may read', + 'mw': 'may write', + 'me': 'may execute', + 'ms': 'may share', + 'mp': 'MPX-specific VMA', + 'gd': 'stack segment growns down', + 'pf': 'pure PFN range', + 'dw': 'disabled write to the mapped file', + 'lo': 'pages are locked in memory', + 'io': 'memory mapped I/O area', + 'sr': 'sequential read advise provided', + 'rr': 'random read advise provided', + 'dc': 'do not copy area on fork', + 'de': 'do not expand area on remapping', + 'ac': 'area is accountable', + 'nr': 'swap space is not reserved for the area', + 'ht': 'area uses huge tlb pages', + 'sf': 'perform synchronous page faults', + 'nl': 'non-linear mapping', + 'ar': 'architecture specific flag', + 'wf': 'wipe on fork', + 'dd': 'do not include area into core dump', + 'sd': 'soft-dirty flag', + 'mm': 'mixed map area', + 'hg': 'huge page advise flag', + 'nh': 'no-huge page advise flag', + 'mg': 'mergable advise flag', + 'bt': 'arm64 BTI guarded page', + 'mt': 'arm64 MTE allocation tags are enabled', + 'um': 'userfaultfd missing pages tracking', + 'uw': 'userfaultfd wprotect pages tracking', + 'ui': 'userfaultfd minor fault', + 'ss': 'shadow/guarded control stack page', + 'sl': 'sealed', + 'lf': 'lock on fault pages', + 'dp': 'always lazily freeable mapping', + 'gu': 'maybe contains guard regions' } for entry in proc_data: @@ -245,7 +258,7 @@ def _process(proc_data: List[Dict]) -> List[Dict]: if 'VmFlags' in entry: entry['VmFlags'] = entry['VmFlags'].split() - entry['VmFlags_pretty'] = [vmflags_map[x] for x in entry['VmFlags']] + entry['VmFlags_pretty'] = [vmflags_map.get(x, x) for x in entry['VmFlags']] return proc_data diff --git a/tests/fixtures/linux-proc/pid_smaps_unknown_flag b/tests/fixtures/linux-proc/pid_smaps_unknown_flag new file mode 100644 index 00000000..403b5be1 --- /dev/null +++ b/tests/fixtures/linux-proc/pid_smaps_unknown_flag @@ -0,0 +1,46 @@ +55a9e753c000-55a9e7570000 r--p 00000000 fd:00 798126 /usr/lib/systemd/systemd +Size: 208 kB +KernelPageSize: 4 kB +MMUPageSize: 4 kB +Rss: 208 kB +Pss: 104 kB +Shared_Clean: 208 kB +Shared_Dirty: 0 kB +Private_Clean: 0 kB +Private_Dirty: 0 kB +Referenced: 208 kB +Anonymous: 0 kB +LazyFree: 0 kB +AnonHugePages: 0 kB +ShmemPmdMapped: 0 kB +FilePmdMapped: 0 kB +Shared_Hugetlb: 0 kB +Private_Hugetlb: 0 kB +Swap: 0 kB +SwapPss: 0 kB +Locked: 0 kB +THPeligible: 0 +VmFlags: rd mr mw me dw sd zz +55a9e7570000-55a9e763a000 r-xp 00034000 fd:00 798126 /usr/lib/systemd/systemd +Size: 808 kB +KernelPageSize: 4 kB +MMUPageSize: 4 kB +Rss: 800 kB +Pss: 378 kB +Shared_Clean: 800 kB +Shared_Dirty: 0 kB +Private_Clean: 0 kB +Private_Dirty: 0 kB +Referenced: 800 kB +Anonymous: 0 kB +LazyFree: 0 kB +AnonHugePages: 0 kB +ShmemPmdMapped: 0 kB +FilePmdMapped: 0 kB +Shared_Hugetlb: 0 kB +Private_Hugetlb: 0 kB +Swap: 0 kB +SwapPss: 0 kB +Locked: 0 kB +THPeligible: 0 +VmFlags: rd ex mr mw me dw sd yy diff --git a/tests/fixtures/linux-proc/pid_smaps_unknown_flag.json b/tests/fixtures/linux-proc/pid_smaps_unknown_flag.json new file mode 100644 index 00000000..2fad71ae --- /dev/null +++ b/tests/fixtures/linux-proc/pid_smaps_unknown_flag.json @@ -0,0 +1 @@ +[{"start":"55a9e753c000","end":"55a9e7570000","perms":["read","private"],"offset":"00000000","maj":"fd","min":"00","inode":798126,"pathname":"/usr/lib/systemd/systemd","Size":208,"KernelPageSize":4,"MMUPageSize":4,"Rss":208,"Pss":104,"Shared_Clean":208,"Shared_Dirty":0,"Private_Clean":0,"Private_Dirty":0,"Referenced":208,"Anonymous":0,"LazyFree":0,"AnonHugePages":0,"ShmemPmdMapped":0,"FilePmdMapped":0,"Shared_Hugetlb":0,"Private_Hugetlb":0,"Swap":0,"SwapPss":0,"Locked":0,"THPeligible":0,"VmFlags":["rd","mr","mw","me","dw","sd","zz"],"VmFlags_pretty":["readable","may read","may write","may execute","disabled write to the mapped file","soft-dirty flag","zz"]},{"start":"55a9e7570000","end":"55a9e763a000","perms":["read","execute","private"],"offset":"00034000","maj":"fd","min":"00","inode":798126,"pathname":"/usr/lib/systemd/systemd","Size":808,"KernelPageSize":4,"MMUPageSize":4,"Rss":800,"Pss":378,"Shared_Clean":800,"Shared_Dirty":0,"Private_Clean":0,"Private_Dirty":0,"Referenced":800,"Anonymous":0,"LazyFree":0,"AnonHugePages":0,"ShmemPmdMapped":0,"FilePmdMapped":0,"Shared_Hugetlb":0,"Private_Hugetlb":0,"Swap":0,"SwapPss":0,"Locked":0,"THPeligible":0,"VmFlags":["rd","ex","mr","mw","me","dw","sd","yy"],"VmFlags_pretty":["readable","executable","may read","may write","may execute","disabled write to the mapped file","soft-dirty flag","yy"]}] diff --git a/tests/test_proc_pid_smaps.py b/tests/test_proc_pid_smaps.py index b579c801..290af663 100644 --- a/tests/test_proc_pid_smaps.py +++ b/tests/test_proc_pid_smaps.py @@ -16,7 +16,10 @@ class MyTests(unittest.TestCase): fixtures = { 'proc_pid_smaps': ( 'fixtures/linux-proc/pid_smaps', - 'fixtures/linux-proc/pid_smaps.json') + 'fixtures/linux-proc/pid_smaps.json'), + 'proc_pid_smaps_unknown_flag': ( + 'fixtures/linux-proc/pid_smaps_unknown_flag', + 'fixtures/linux-proc/pid_smaps_unknown_flag.json') } for file, filepaths in fixtures.items(): @@ -39,6 +42,13 @@ class MyTests(unittest.TestCase): self.assertEqual(jc.parsers.proc_pid_smaps.parse(self.f_in['proc_pid_smaps'], quiet=True), self.f_json['proc_pid_smaps']) + def test_proc_pid_smaps_unknown_flag(self): + """ + Test '/proc//smaps' with an unknown flag + """ + self.assertEqual(jc.parsers.proc_pid_smaps.parse(self.f_in['proc_pid_smaps_unknown_flag'], quiet=True), + self.f_json['proc_pid_smaps_unknown_flag']) + if __name__ == '__main__': unittest.main() From e33a81269c1871b80aa916612f72f533d14b2eff Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 27 Feb 2026 14:54:15 -0800 Subject: [PATCH 03/10] update os matrix and python versions for tests --- .github/workflows/pythonapp.yml | 115 ++++++++++++++++---------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index c2775164..f9106a54 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -9,69 +9,14 @@ on: - "**/*.py" jobs: - very_old_python: - if: github.event.pull_request.draft == false - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-13, windows-2022] - python-version: ["3.6"] - - steps: - - uses: actions/checkout@v3 - - name: "Set up timezone to America/Los_Angeles" - uses: szenius/set-timezone@v1.2 - with: - timezoneLinux: "America/Los_Angeles" - timezoneMacos: "America/Los_Angeles" - timezoneWindows: "Pacific Standard Time" - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Test with unittest - run: | - python -m unittest discover tests - - old_python: - if: github.event.pull_request.draft == false - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-13, ubuntu-22.04, windows-2022] - python-version: ["3.7", "3.8", "3.9", "3.10"] - - steps: - - uses: actions/checkout@v3 - - name: "Set up timezone to America/Los_Angeles" - uses: szenius/set-timezone@v1.2 - with: - timezoneLinux: "America/Los_Angeles" - timezoneMacos: "America/Los_Angeles" - timezoneWindows: "Pacific Standard Time" - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Test with unittest - run: | - python -m unittest discover tests latest_python: if: github.event.pull_request.draft == false runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-latest, ubuntu-latest, windows-latest] - python-version: ["3.11", "3.12"] + os: [macos-15-intel, macos-latest, ubuntu-latest, ubuntu-24.04-arm, windows-latest] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v3 @@ -92,3 +37,59 @@ jobs: - name: Test with unittest run: | python -m unittest discover tests + + # very_old_python: + # if: github.event.pull_request.draft == false + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # os: [macos-13, windows-2022] + # python-version: ["3.6"] + + # steps: + # - uses: actions/checkout@v3 + # - name: "Set up timezone to America/Los_Angeles" + # uses: szenius/set-timezone@v1.2 + # with: + # timezoneLinux: "America/Los_Angeles" + # timezoneMacos: "America/Los_Angeles" + # timezoneWindows: "Pacific Standard Time" + # - name: Set up Python ${{ matrix.python-version }} + # uses: actions/setup-python@v4 + # with: + # python-version: ${{ matrix.python-version }} + # - name: Install dependencies + # run: | + # python -m pip install --upgrade pip + # pip install -r requirements.txt + # - name: Test with unittest + # run: | + # python -m unittest discover tests + + # old_python: + # if: github.event.pull_request.draft == false + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # os: [macos-13, ubuntu-22.04, windows-2022] + # python-version: ["3.7", "3.8", "3.9", "3.10"] + + # steps: + # - uses: actions/checkout@v3 + # - name: "Set up timezone to America/Los_Angeles" + # uses: szenius/set-timezone@v1.2 + # with: + # timezoneLinux: "America/Los_Angeles" + # timezoneMacos: "America/Los_Angeles" + # timezoneWindows: "Pacific Standard Time" + # - name: Set up Python ${{ matrix.python-version }} + # uses: actions/setup-python@v4 + # with: + # python-version: ${{ matrix.python-version }} + # - name: Install dependencies + # run: | + # python -m pip install --upgrade pip + # pip install -r requirements.txt + # - name: Test with unittest + # run: | + # python -m unittest discover tests From 3d9554baec74bedaa1d61926894cf5abdb5282de Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 27 Feb 2026 14:55:45 -0800 Subject: [PATCH 04/10] force tests --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 71a891a3..9f5caa8d 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,7 @@ import setuptools +# force tests + with open('README.md', 'r') as f: long_description = f.read() From 51543437d7ed18c8136d4760aff1889e3c7cb8e3 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 27 Feb 2026 14:57:59 -0800 Subject: [PATCH 05/10] remove comment --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 9f5caa8d..38cdaae1 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ import setuptools -# force tests with open('README.md', 'r') as f: long_description = f.read() From 936432d8791266b8255113f38bb09fe1706235dd Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 6 Mar 2026 12:59:16 -0800 Subject: [PATCH 06/10] Fix parsing blank targe in verbose output #675 --- CHANGELOG | 4 +++- jc/parsers/iptables.py | 11 +++++++++-- tests/fixtures/generic/iptables-no-jump2.json | 1 + tests/fixtures/generic/iptables-no-jump2.out | 3 +++ tests/test_iptables.py | 12 ++++++++++++ 5 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/generic/iptables-no-jump2.json create mode 100644 tests/fixtures/generic/iptables-no-jump2.out diff --git a/CHANGELOG b/CHANGELOG index 67782fa5..d2c15c74 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ jc changelog -20260227 v1.25.7 +20260306 v1.25.7 +- Fix `proc-pid-smaps` proc parser when unknown VmFlags are output +- Fix `iptables` command parser when Target is blank and verbose output is used 20251012 v1.25.6 - Add `net-localgroup` Windows command parser diff --git a/jc/parsers/iptables.py b/jc/parsers/iptables.py index 9dd8b7b5..f5d21adb 100644 --- a/jc/parsers/iptables.py +++ b/jc/parsers/iptables.py @@ -173,7 +173,7 @@ import jc.utils class info(): """Provides parser metadata (version, author, etc.)""" - version = '1.12' + version = '1.13' description = '`iptables` command parser' author = 'Kelly Brazil' author_email = 'kellyjonbrazil@gmail.com' @@ -294,9 +294,16 @@ def parse(data, raw=False, quiet=False): else: # sometimes the "target" column is blank. Stuff in a dummy character - if headers[0] == 'target' and line.startswith(' '): + opt_values = {'--', '-f', '!f'} + line_split = line.split() + if headers[0] == 'target' and line.startswith(' '): # standard output line = '\u2063' + line + elif headers[0] == 'pkts' and line_split[3] in opt_values: # verbose output + first_section = line_split[:2] + second_section = line_split[2:] + line = ' '.join(first_section) + ' \u2063 ' + ' '.join(second_section) + rule = line.split(maxsplit=len(headers) - 1) temp_rule = dict(zip(headers, rule)) if temp_rule: diff --git a/tests/fixtures/generic/iptables-no-jump2.json b/tests/fixtures/generic/iptables-no-jump2.json new file mode 100644 index 00000000..ea217a0e --- /dev/null +++ b/tests/fixtures/generic/iptables-no-jump2.json @@ -0,0 +1 @@ +[{"chain":"INPUT","default_policy":"ACCEPT","default_packets":0,"default_bytes":0,"rules":[{"pkts":17,"bytes":1172,"target":null,"prot":"all","opt":null,"in":"*","out":"*","source":"0.0.0.0/0","destination":"0.0.0.0/0"}]}] diff --git a/tests/fixtures/generic/iptables-no-jump2.out b/tests/fixtures/generic/iptables-no-jump2.out new file mode 100644 index 00000000..0422051e --- /dev/null +++ b/tests/fixtures/generic/iptables-no-jump2.out @@ -0,0 +1,3 @@ +Chain INPUT (policy ACCEPT 0 packets, 0 bytes) + pkts bytes target prot opt in out source destination + 17 1172 all -- * * 0.0.0.0/0 0.0.0.0/0 diff --git a/tests/test_iptables.py b/tests/test_iptables.py index e94ea806..157dff63 100644 --- a/tests/test_iptables.py +++ b/tests/test_iptables.py @@ -48,6 +48,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/iptables-no-jump.out'), 'r', encoding='utf-8') as f: generic_iptables_no_jump = f.read() + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/iptables-no-jump2.out'), 'r', encoding='utf-8') as f: + generic_iptables_no_jump2 = f.read() + # output with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/iptables-filter.json'), 'r', encoding='utf-8') as f: centos_7_7_iptables_filter_json = json.loads(f.read()) @@ -88,6 +91,9 @@ class MyTests(unittest.TestCase): with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/iptables-no-jump.json'), 'r', encoding='utf-8') as f: generic_iptables_no_jump_json = json.loads(f.read()) + with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/iptables-no-jump2.json'), 'r', encoding='utf-8') as f: + generic_iptables_no_jump2_json = json.loads(f.read()) + def test_iptables_nodata(self): """ @@ -173,6 +179,12 @@ class MyTests(unittest.TestCase): """ self.assertEqual(jc.parsers.iptables.parse(self.generic_iptables_no_jump, quiet=True), self.generic_iptables_no_jump_json) + def test_iptables_no_jump2_generic(self): + """ + Test 'sudo iptables' with no jump target and verbose output + """ + self.assertEqual(jc.parsers.iptables.parse(self.generic_iptables_no_jump2, quiet=True), self.generic_iptables_no_jump2_json) + def test_iptables_x_option_format(self): """ Test iptables -x From 441bcbde80e8248204329a85e2c099601caaadbe Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Fri, 6 Mar 2026 15:50:52 -0800 Subject: [PATCH 07/10] fix indent on help text so lines don't wrap over 80 chars. Also fix mypy uncovered value assignment issue. --- jc/cli.py | 16 ++++++++++++---- jc/cli_data.py | 52 +++++++++++++++++++++++++------------------------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/jc/cli.py b/jc/cli.py index 855aaa78..7f559286 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -300,8 +300,8 @@ class JcCli(): Pages the parser documentation if a parser is found in the arguments, otherwise the general help text is printed. """ - self.indent = 4 - self.pad = 22 + self.indent = 2 + self.pad = 21 if self.show_categories: utils._safe_print(self.parser_categories_text()) @@ -569,7 +569,11 @@ class JcCli(): if self.debug: raise - error_msg = os.strerror(e.errno) + if e.errno: + error_msg = os.strerror(e.errno) + else: + error_msg = "no further information provided" + utils.error_message([ f'"{file}" file could not be opened: {error_msg}.' ]) @@ -594,7 +598,11 @@ class JcCli(): if self.debug: raise - error_msg = os.strerror(e.errno) + if e.errno: + error_msg = os.strerror(e.errno) + else: + error_msg = "no further information provided" + utils.error_message([ f'"{self.magic_run_command_str}" command could not be run: {error_msg}.' ]) diff --git a/jc/cli_data.py b/jc/cli_data.py index 261bc989..6cdaae10 100644 --- a/jc/cli_data.py +++ b/jc/cli_data.py @@ -62,52 +62,52 @@ jc converts the output of many commands, file-types, and strings to JSON or YAML Usage: - Standard syntax: + Standard syntax: - COMMAND | jc [SLICE] [OPTIONS] PARSER + COMMAND | jc [SLICE] [OPTIONS] PARSER - cat FILE | jc [SLICE] [OPTIONS] PARSER + cat FILE | jc [SLICE] [OPTIONS] PARSER - echo STRING | jc [SLICE] [OPTIONS] PARSER + echo STRING | jc [SLICE] [OPTIONS] PARSER - Magic syntax: + Magic syntax: - jc [SLICE] [OPTIONS] COMMAND + jc [SLICE] [OPTIONS] COMMAND - jc [SLICE] [OPTIONS] /proc/ + jc [SLICE] [OPTIONS] /proc/ Parsers: ''' slicetext_string: str = '''\ Slice: - [start]:[end] + [start]:[end] - start: [[-]index] - Zero-based start line, negative index for - counting from the end + start: [[-]index] - Zero-based start line, negative index for + counting from the end - end: [[-]index] - Zero-based end line (excluding the index), - negative index for counting from the end + end: [[-]index] - Zero-based end line (excluding the index), + negative index for counting from the end ''' helptext_end_string: str = '''\ Examples: - Standard Syntax: - $ dig www.google.com | jc --pretty --dig - $ cat /proc/meminfo | jc --pretty --proc + Standard Syntax: + $ dig www.google.com | jc --pretty --dig + $ cat /proc/meminfo | jc --pretty --proc - Magic Syntax: - $ jc --pretty dig www.google.com - $ jc --pretty /proc/meminfo + Magic Syntax: + $ jc --pretty dig www.google.com + $ jc --pretty /proc/meminfo - Line Slicing: - $ cat output.txt | jc 4:15 -- # Parse from line 4 to 14 - # with (zero-based) + Line Slicing: + $ cat output.txt | jc 4:15 -- # Parse from line 4 to 14 + # with (zero-based) - Parser Documentation: - $ jc --help --dig + Parser Documentation: + $ jc --help --dig - More Help: - $ jc -hh # show hidden parsers - $ jc -hhh # list parsers by category tags + More Help: + $ jc -hh # show hidden parsers + $ jc -hhh # list parsers by category tags ''' \ No newline at end of file From e01287b329a48019e5864e0a8fb4aa840caebeb6 Mon Sep 17 00:00:00 2001 From: Kelly Brazil Date: Mon, 9 Mar 2026 17:35:33 -0700 Subject: [PATCH 08/10] add stats fields to json output. #676 --- CHANGELOG | 3 +- jc/parsers/rsync.py | 114 ++++++++++++++++-- jc/parsers/rsync_s.py | 101 ++++++++++++++-- .../generic/rsync-i-stats-streaming.json | 1 + tests/fixtures/generic/rsync-i-stats.json | 1 + tests/fixtures/generic/rsync-i-stats.out | 56 +++++++++ tests/test_rsync.py | 12 ++ tests/test_rsync_s.py | 11 ++ 8 files changed, 275 insertions(+), 24 deletions(-) create mode 100644 tests/fixtures/generic/rsync-i-stats-streaming.json create mode 100644 tests/fixtures/generic/rsync-i-stats.json create mode 100644 tests/fixtures/generic/rsync-i-stats.out diff --git a/CHANGELOG b/CHANGELOG index d2c15c74..aba657d9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ jc changelog -20260306 v1.25.7 +20260309 v1.25.7 +- Enhance `rsync` and `rsync-s` parsers to add `--stats` or `--info=stats[1-3]` fields - Fix `proc-pid-smaps` proc parser when unknown VmFlags are output - Fix `iptables` command parser when Target is blank and verbose output is used diff --git a/jc/parsers/rsync.py b/jc/parsers/rsync.py index 195aae77..cee983b3 100644 --- a/jc/parsers/rsync.py +++ b/jc/parsers/rsync.py @@ -4,6 +4,8 @@ Supports the `-i` or `--itemize-changes` options with all levels of verbosity. This parser will process the `STDOUT` output or a log file generated with the `--log-file` option. +The `--stats` or `--info=stats[1-3]` options are also supported. + Usage (cli): $ rsync -i -a source/ dest | jc --rsync @@ -37,7 +39,21 @@ Schema: "false_alarms": integer, "data": integer, "bytes_sec": float, - "speedup": float + "speedup": float, + "total_files": integer, + "regular_files": integer, + "dir_files": integer, + "total_created_files": integer, + "created_regular_files": integer, + "created_dir_files": integer, + "deleted_files": integer, + "transferred_files": integer, + "transferred_file_size": integer, + "literal_data": integer, + "matched_data": integer, + "file_list_size": integer, + "file_list_generation_time": float, + "file_list_transfer_time": float, }, "files": [ { @@ -62,6 +78,8 @@ Schema: } ] + Size values are in bytes. + [0] 'file sent', 'file received', 'local change or creation', 'hard link', 'not updated', 'message' [1] 'file', 'directory', 'symlink', 'device', 'special file' @@ -137,7 +155,7 @@ import jc.utils class info(): """Provides parser metadata (version, author, etc.)""" - version = '1.2' + version = '1.3' description = '`rsync` command parser' author = 'Kelly Brazil' author_email = 'kellyjonbrazil@gmail.com' @@ -163,10 +181,16 @@ def _process(proc_data: List[Dict]) -> List[Dict]: """ int_list = { 'process', 'sent', 'received', 'total_size', 'matches', 'hash_hits', - 'false_alarms', 'data' + 'false_alarms', 'data', 'total_files', 'regular_files', 'dir_files', + 'total_created_files', 'created_regular_files', 'created_dir_files', + 'deleted_files', 'transferred_files', 'transferred_file_size', + 'literal_data', 'matched_data', 'file_list_size' } - float_list = {'bytes_sec', 'speedup'} + float_list = { + 'bytes_sec', 'speedup', 'file_list_generation_time', + 'file_list_transfer_time' + } for item in proc_data: for key in item['summary']: @@ -338,6 +362,17 @@ def parse( stat2_line_log_v_re = re.compile(r'(?P\d\d\d\d/\d\d/\d\d)\s+(?P