1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2026-04-03 17:44:07 +02:00

Merge branch 'dev' into fix/dir-lstrip-drive-letter

This commit is contained in:
Kelly Brazil
2026-03-30 11:46:32 -07:00
committed by GitHub
5 changed files with 104 additions and 18 deletions

View File

@@ -1,11 +1,13 @@
jc changelog
20260316 v1.25.7
20260330 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 `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
20251012 v1.25.6

View File

@@ -219,7 +219,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '2.4'
version = '2.5'
description = '`ifconfig` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -264,7 +264,7 @@ def _process(proc_data: List[JSONDictType]) -> List[JSONDictType]:
try:
if entry['ipv4_mask'].startswith('0x'):
new_mask = entry['ipv4_mask']
new_mask = new_mask.lstrip('0x')
new_mask = new_mask[2:]
new_mask = '.'.join(str(int(i, 16)) for i in [new_mask[i:i + 2] for i in range(0, len(new_mask), 2)])
entry['ipv4_mask'] = new_mask
except (ValueError, TypeError, AttributeError):
@@ -289,7 +289,7 @@ def _process(proc_data: List[JSONDictType]) -> List[JSONDictType]:
try:
if ip_address['mask'].startswith('0x'):
new_mask = ip_address['mask']
new_mask = new_mask.lstrip('0x')
new_mask = new_mask[2:]
new_mask = '.'.join(str(int(i, 16)) for i in [new_mask[i:i + 2] for i in range(0, len(new_mask), 2)])
ip_address['mask'] = new_mask
except (ValueError, TypeError, AttributeError):

View File

@@ -26,7 +26,10 @@ Schema:
"license": string,
"location": string,
"requires": string,
"required_by": string
"required_by": string,
"files": [
string
]
}
]
@@ -60,13 +63,13 @@ Examples:
}
]
"""
from typing import List, Dict, Optional
from typing import List, Dict
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.5'
version = '1.6'
description = '`pip show` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -120,6 +123,22 @@ def parse(
last_key: str = ''
last_key_data: List = []
def flush_last_key_data() -> None:
"""Append buffered continuation lines to the previous field."""
nonlocal last_key_data
if not last_key_data:
return
if last_key == 'files':
package[last_key].extend(last_key_data)
else:
if not isinstance(package[last_key], str):
package[last_key] = ''
package[last_key] = package[last_key] + '\n' + '\n'.join(last_key_data)
last_key_data = []
# Clear any blank lines
cleandata = list(filter(None, data.splitlines()))
@@ -127,8 +146,7 @@ def parse(
for row in cleandata:
if row.startswith('---'):
if last_key_data:
package[last_key] = package[last_key] + '\n' + '\n'.join(last_key_data)
flush_last_key_data()
raw_output.append(package)
package = {}
@@ -137,17 +155,17 @@ def parse(
continue
if not row.startswith(' '):
item_key = row.split(': ', maxsplit=1)[0].lower().replace('-', '_')
item_value: Optional[str] = row.split(': ', maxsplit=1)[1]
item_key, item_value = row.split(':', maxsplit=1)
item_key = item_key.lower().replace('-', '_')
item_value = item_value.lstrip()
if item_value == '':
if item_key == 'files':
item_value = []
elif item_value == '':
item_value = None
if last_key_data and last_key != item_key:
if not isinstance(package[last_key], str):
package[last_key] = ''
package[last_key] = package[last_key] + '\n' + '\n'.join(last_key_data)
last_key_data = []
flush_last_key_data()
package[item_key] = item_value
last_key = item_key
@@ -158,8 +176,7 @@ def parse(
continue
if package:
if last_key_data:
package[last_key] = package[last_key] + '\n' + '\n'.join(last_key_data)
flush_last_key_data()
raw_output.append(package)

View File

@@ -148,6 +148,21 @@ class MyTests(unittest.TestCase):
"""
self.assertEqual(jc.parsers.ifconfig.parse(self.osx_freebsd12_ifconfig_extra_fields4, quiet=True), self.freebsd12_ifconfig_extra_fields4_json)
def test_ifconfig_hex_mask_all_zeros(self):
"""
Test 'ifconfig' with 0x00000000 netmask (FreeBSD/macOS hex format).
Regression test: lstrip('0x') incorrectly strips leading '0' chars
from the hex digits, producing wrong mask for all-zero masks.
"""
data = (
'lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384\n'
'\toptions=1203<RXCSUM,TXCSUM,TXSTATUS,SW_TIMESTAMP>\n'
'\tinet 192.168.1.1 netmask 0x00000000\n'
)
result = jc.parsers.ifconfig.parse(data, quiet=True)
self.assertEqual(result[0]['ipv4_mask'], '0.0.0.0')
self.assertEqual(result[0]['ipv4'][0]['mask'], '0.0.0.0')
def test_ifconfig_utun_ipv4(self):
"""
Test 'ifconfig' with ipv4 utun addresses (macOS)

View File

@@ -89,6 +89,58 @@ class MyTests(unittest.TestCase):
"""
self.assertEqual(jc.parsers.pip_show.parse(self.generic_pip_show_multiline_license_first_blank, quiet=True), self.generic_pip_show_multiline_license_first_blank_json)
def test_pip_show_files_section(self):
"""
Test 'pip show -f' output with a files section
"""
data = """\
Name: jc
Version: 1.25.4
Summary: Converts the output of popular command-line tools and file-types to JSON.
Home-page: https://github.com/kellyjonbrazil/jc
Author: Kelly Brazil
Author-email: kelly@gmail.com
License: MIT
Location: /home/pi/.local/lib/python3.11/site-packages
Requires: Pygments, ruamel.yaml, xmltodict
Required-by: pypiwifi
Files:
../../../bin/jc
jc-1.25.4.dist-info/RECORD
"""
expected = [{
'name': 'jc',
'version': '1.25.4',
'summary': 'Converts the output of popular command-line tools and file-types to JSON.',
'home_page': 'https://github.com/kellyjonbrazil/jc',
'author': 'Kelly Brazil',
'author_email': 'kelly@gmail.com',
'license': 'MIT',
'location': '/home/pi/.local/lib/python3.11/site-packages',
'requires': 'Pygments, ruamel.yaml, xmltodict',
'required_by': 'pypiwifi',
'files': ['../../../bin/jc', 'jc-1.25.4.dist-info/RECORD']
}]
self.assertEqual(jc.parsers.pip_show.parse(data, quiet=True), expected)
def test_pip_show_files_section_with_following_field(self):
"""
Test 'pip show -f' output when the files section is followed by a new field
"""
data = """\
Name: jc
Files:
../../../bin/jc
jc-1.25.4.dist-info/RECORD
Foo: bar
"""
expected = [{
'name': 'jc',
'files': ['../../../bin/jc', 'jc-1.25.4.dist-info/RECORD'],
'foo': 'bar'
}]
self.assertEqual(jc.parsers.pip_show.parse(data, quiet=True), expected)
if __name__ == '__main__':
unittest.main()