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

Compare commits

..

127 Commits

Author SHA1 Message Date
Kelly Brazil
9fd13e6987 Merge pull request #671 from kellyjonbrazil/dev
Dev v1.25.6
2025-10-12 18:52:52 -07:00
Kelly Brazil
1feb000231 doc update 2025-10-12 18:09:31 -07:00
Kelly Brazil
d270ddc6ce fixup traceroute and traceroute-s parsers 2025-10-12 17:58:37 -07:00
Shintaro Kojima
07ef285b06 Add traceroute streaming parser - traceroute-s (#669)
* test: split out test fixtures for long ipv6 traceroute for consistency

* refactor(jc/parsers/traceroute): remove duplicate ParseError class

* refactor(jc/parsers/traceroute): pre-process data in _loads() for easy-to-reuse

* refactor(jc/parsers/traceroute): split hop serialization into separate function to reuse

* refactor(jc/parsers/traceroute): simplify numeric conversion and make it reusable for traceroute_s

* fix(jc/parsers/traceroute): stricter regex to match traceroute headers only

* feat(jc/parsers/traceroute_s): v1.0 implementation

* fix(jc/parsers/traceroute): revert "_" prefix in function and class names

* fixup! fix(jc/parsers/traceroute): revert "_" prefix in function and class names

* chore(jc/parsers/traceroute): update the author information
2025-10-12 10:21:16 -07:00
Kelly Brazil
467ad60e20 Fix for who output containing process name #663 2025-09-08 17:14:37 -07:00
Kelly Brazil
91b3572ead parser version bump 2025-09-08 15:04:48 -07:00
Brian Wo
60caa17eb9 add yay as magic command for pacman (#657)
yay is a popular pacman wrapper that adds extra functionality to
standard pacman. It has the same output format as pacman.

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2025-09-08 14:59:55 -07:00
Kelly Brazil
eea31ddbe4 add .DS_Store to gitignore 2025-09-08 14:56:43 -07:00
Kelly Brazil
61793bba31 fix net_localgroup tests 2025-09-08 14:54:45 -07:00
Kelly Brazil
51e4a476ba fixup net_localgroup parser 2025-09-08 14:44:13 -07:00
Kelly Brazil
883f0c9cf7 net-user parser fixup 2025-09-08 14:25:44 -07:00
Kelly Brazil
ffaf455d43 doc updates 2025-09-04 16:59:21 -07:00
Kelly Brazil
b520532578 fixup route-print parser 2025-09-04 16:43:18 -07:00
Jose E. Rodriguez
3b1af1a9b6 Feat: Introduce net-localgroup net-user and route-print parsers (#602)
* feat(patch): introduce net localgroup, net user, and route print parsers

* fix: fix net user parsing error

* fix: address PR findings

---------

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2025-09-04 14:47:28 -07:00
Kelly Brazil
0fa5f7f67f add test 2025-08-03 17:19:21 -07:00
Kelly Brazil
a77298e560 add cable_pairing field to fix #662 2025-08-01 07:41:45 -07:00
Kelly Brazil
5f75df2e4b remove force test comment 2025-07-24 13:13:21 -07:00
Kelly Brazil
38d169f4d0 force tests 2025-07-24 13:09:44 -07:00
Kelly Brazil
e737dde9e0 windows 2019 runner deprecated - move to windows 2022 2025-07-24 13:08:15 -07:00
Kelly Brazil
6e49c87575 update tests 2025-07-24 13:02:37 -07:00
Kelly Brazil
6afa6b449e add unit and byte conversion fields 2025-07-08 12:30:41 -07:00
Kelly Brazil
424d9c1347 add unit string fields and topline bytes fields 2025-07-08 11:45:47 -07:00
Kelly Brazil
10d1ab1c74 add bytes fields 2025-07-08 08:50:12 -07:00
Kelly Brazil
5db256c3e7 add x509-crl parser tests 2025-06-06 15:23:42 -07:00
Kelly Brazil
7f7dcc35ec help screen update 2025-05-28 14:54:29 -07:00
Kelly Brazil
edcde58cb9 remove unused Dict type import 2025-05-28 13:18:00 -07:00
Kelly Brazil
3a7ca118ff doc updates 2025-05-27 09:06:26 -07:00
Kelly Brazil
f36c713110 doc fix 2025-05-20 17:58:04 -07:00
Kelly Brazil
0db2ad2f5e doc fix 2025-05-20 17:56:15 -07:00
Kelly Brazil
816c38e1fe now returns only a single object, not an array of CRL objects 2025-05-20 17:47:05 -07:00
Kelly Brazil
e4cf7b502e doc update 2025-05-18 20:16:58 -07:00
Kelly Brazil
4ca7179481 add x509-crl parser 2025-05-18 20:09:05 -07:00
Kelly Brazil
bebd6a60fb add team-port.config and fix for blank values 2025-05-18 19:24:54 -07:00
Kelly Brazil
5d7940a89b version bump 2025-05-18 19:24:19 -07:00
Kelly Brazil
ac8120e1fe Merge pull request #655 from kellyjonbrazil/dev
Dev v1.25.5
2025-05-10 11:38:04 -07:00
Kelly Brazil
a854f1d6b0 doc update 2025-05-10 11:25:41 -07:00
Kelly Brazil
7c9e92e284 add test for team.config with JSON 2025-05-05 14:48:12 -07:00
Kelly Brazil
4c2775970d change team_config to object instead of list 2025-05-05 11:34:30 -07:00
Kelly Brazil
ba0fc5dad9 doc update 2025-05-03 13:42:34 -07:00
Kelly Brazil
222d1b7255 support json value in team.config field 2025-05-03 13:39:32 -07:00
Kelly Brazil
34fda0905f force test run 2025-04-15 08:56:36 -07:00
Kelly Brazil
ccbe36e712 Create very_old, old, and latest python runs
needed due to loss of python 3.6 support on some runners
2025-04-15 08:55:41 -07:00
Kelly Brazil
bb1136c33c Merge branch 'dev' of https://github.com/kellyjonbrazil/jc into dev 2025-04-15 08:51:39 -07:00
Kelly Brazil
c6a13aa4d8 force test run 2025-04-15 08:51:35 -07:00
Kelly Brazil
d04a175d36 Update to ubuntu-22.04 on old version run 2025-04-15 08:50:30 -07:00
Kelly Brazil
966df71566 add default policy stats to iptables parser 2025-04-15 08:41:30 -07:00
Kelly Brazil
c8599253fb formatting 2025-04-14 15:30:35 -07:00
Kelly Brazil
65d9cc718f version bump 2025-04-13 18:14:15 -07:00
Kelly Brazil
9b4190f1e6 fix mount on macOS when filesystem contains parenthesis 2025-04-13 18:13:31 -07:00
Kelly Brazil
c519657d85 allow IDNA2008 encoded email addresses with warning 2025-04-12 10:50:26 -07:00
Kelly Brazil
d5cceb77e9 remove extraneous file 2025-04-04 16:16:47 -07:00
Kelly Brazil
5cde110afb set bytes conversion to posix mode 2025-04-04 16:14:20 -07:00
Kelly Brazil
65aa6c1a9a update copyright date 2025-04-02 12:06:59 -07:00
Kelly Brazil
5394d7f62d doc update 2025-04-02 11:37:24 -07:00
Jake Ob
ebbd4e9320 Fix broken controller parser schema to include power state prop (#652)
This commit fixes issue #627

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2025-04-02 11:32:19 -07:00
Kelly Brazil
834acfb2d6 update lsblk tests 2025-03-31 17:06:41 -07:00
Kelly Brazil
1182c9f263 fix for missing dB value 2025-03-31 16:50:46 -07:00
Kelly Brazil
ed3046bb1b remove type annotations since they are not used 2025-03-31 16:34:38 -07:00
Kelly Brazil
ca73076b56 make empty mountpoints list instead of null 2025-03-31 14:29:27 -07:00
Kelly Brazil
9efc014dce doc update 2025-03-31 14:17:51 -07:00
Kelly Brazil
dbe1757a48 add mountpoints array field and convert sizes to bytes 2025-03-31 14:16:17 -07:00
Kelly Brazil
1c09289d95 fix time parser for missing centiseconds 2025-03-31 12:16:45 -07:00
Kelly Brazil
e9ccedf0d7 doc update 2025-03-30 12:38:41 -07:00
Michel Lind
fb4b4eeb58 Adjust for removal of typing.ByteString in Python 3.14 (#639)
`typing.ByteString` has been removed:

https://docs.python.org/3.14/whatsnew/3.14.html

The modernizing guide suggests `collections.abc.ByteString` which has
also been removed; the recommendation is to use either:

- just `bytes`
- `collections.abc.Buffer`
- a union of `bytes`, `bytesarray`, etc.

https://typing.readthedocs.io/en/latest/guides/modernizing.html#modernizing-byte-string

Per discussion, using `bytes` should suffice

Signed-off-by: Michel Lind <salimma@fedoraproject.org>
2025-03-30 12:34:54 -07:00
pettai
896891ad9e Switch TZ to a generic name (#635)
Switch TZ to a more generic name that works under minimal chrooted builds. (America/Los_Angeles, Pacific, PST, PST8PDT)
This fixes pbuilder builds on Ubuntu

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2025-03-30 12:33:45 -07:00
Kelly Brazil
7fda79d841 allow parser to deal with null input 2024-12-20 16:31:38 -08:00
Kelly Brazil
8995ac686a doc update 2024-12-20 15:30:41 -08:00
Kelly Brazil
006047553d version bump to v1.25.5 2024-12-20 15:29:07 -08:00
Kelly Brazil
c56e38c66c Merge pull request #625 from kellyjonbrazil/master
Merge pull request #618 sync dev
2024-12-20 23:26:25 +00:00
Kelly Brazil
ee3b873ef4 Merge branch 'dev' into master 2024-12-20 23:24:53 +00:00
Eden Refael
a39cb05228 created the amixer sget command parser - READY FOR REVIEW (#616)
* created the amixer first skeleton

* push testing and integrate this commit and branch with issue: #591

* #591 checks the input data with jc utils

* created the data parser of the sget control of the amixer sget <controller> command.

* test commit - just for tests

* another test commit

* another test commit

* created a dedicated pseudo algorithm for the amixer sget and tried various of strings.

* orginized the docstring with general explanation about the tool and the amixer tool output and algorithm of the input parsing and input examples.

* created raw implementation, but it's raw either or either.

* orginized the content inside the amixer parser

* removed endpoint name

* added amixer to the jc parser in lib

* more explanations

* added tests for the amixer sget

* added tests for the amixer sget

* fine versioning fix

* created docstring+another explanations seperated.

* created the amixer parser docu

* added the amixer in alphabet order to the json convert lib

* Fix PEP 8: E302 violation as part of boy scout principle

* deleted not necessary file

* fixed the spaces between sections in the amixer description

* resolved commits such as amixer module docstring and preperations for  parser for raw=False.

* Revert "Fix PEP 8: E302 violation as part of boy scout principle"

This reverts commit 241d1a1c63.

* created the dedicated _process for raw=False

* created the dedicated _process for raw=False

* added tests for the _process raw=False.

* changed keys to be lowercase snake-case - Change 'dB' to 'db'

* added more dB -> db changes and used int convertor of the jc utils

---------

Co-authored-by: EdenRafael <eden.refael@kazuar.com>
Co-authored-by: Eden Refael <edeenraf@hotmail.com>
Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-12-20 15:06:38 -08:00
Kelly Brazil
f54ceaa793 Merge pull request #619 from Luigi31415/add-tz-to-tests
Timezone Testing with TZ Env Variable
2024-11-29 01:02:31 +00:00
Hamza Saht
99cce72244 fix: Add TZ for runtests.sh 2024-11-27 20:31:47 +03:00
Kelly Brazil
0c40e3a3c9 Merge pull request #618 from kellyjonbrazil/dev
Dev v1.25.4
2024-11-25 20:39:34 -08:00
Kelly Brazil
025d00ecd2 Merge branch 'master' into dev 2024-11-25 20:33:20 -08:00
Kelly Brazil
a2b5d41308 minor fix to iw-scan to parse more fields 2024-11-25 20:19:40 -08:00
Kelly Brazil
366589268e update calculated fields 2024-11-25 17:16:30 -08:00
Kelly Brazil
53dd28b65e doc update 2024-11-24 18:59:30 -08:00
Kelly Brazil
0be6528aff convert size fields and fix issue for optional_deps without a description 2024-11-24 18:13:08 -08:00
Kelly Brazil
6ceaa7749e doc update 2024-11-24 17:30:49 -08:00
Kelly Brazil
6ae2e17ea1 add examples and tests 2024-11-24 17:30:03 -08:00
Kelly Brazil
ef9ca9322e Some processing completed 2024-11-24 16:02:59 -08:00
Kelly Brazil
95cba21d73 initial pacman parser 2024-11-24 14:57:55 -08:00
Kelly Brazil
be1dd031f1 doc update 2024-11-24 13:38:10 -08:00
Hamza Saht
7fbe1e9f21 Add WireGuard (wg) Command Output Parser (#606)
* feat: add parser to parse the output of wg

* fixup! feat: add parser to parse the output of wg

* feat: Add tests for windows 10

---------

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-11-24 13:34:58 -08:00
Hamza Saht
7d33850d43 fix: Edit pythonapp.yml workflow to run on non-draft PRs (#617) 2024-11-24 13:24:04 -08:00
Kelly Brazil
7887789d0d coerce non-json-serializable objects to strings 2024-11-20 13:49:27 -08:00
Kelly Brazil
7ddd2a4ce2 fix yaml parser to support values starting with an equal sign 2024-11-19 14:12:21 -08:00
Kelly Brazil
34ab34cc66 doc update 2024-11-19 12:15:18 -08:00
pettai
a8b231da81 nsd-control parser update, zone transfer status (#607)
* add more parsing functionality for transfering state

* add additional/conditional fields into the schema

---------

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-11-19 12:12:43 -08:00
Kelly Brazil
2278c7ecab doc update 2024-11-19 10:51:16 -08:00
Kelly Brazil
4cb88977cc add long ipv6 test 2024-11-19 10:51:10 -08:00
Aleksey Lobanov
0af4a3a5d7 fix: New RE_PROBE_IPV6_ONLY with better complexity in worst case (#609)
Example ':'*100

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-11-19 10:43:24 -08:00
Kelly Brazil
c0f9b705c6 doc update 2024-11-19 10:33:19 -08:00
Hamza Saht
fa416083f2 Fix/spaces in program name (#608)
* fix: enforce word boundaries while checking state presence

* fix: add tests for the special netstat case with space in process name

---------

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-11-19 10:28:32 -08:00
Kelly Brazil
e858faa746 fix for contiguous packages with the same name 2024-11-18 16:21:40 -08:00
Kelly Brazil
a8f769eea3 doc update 2024-10-20 09:50:36 -07:00
Kelly Brazil
26133261f9 use jc.utils.timestamp() for datetime conversions 2024-10-20 09:45:08 -07:00
Kelly Brazil
6072ea0ec7 doc update 2024-10-18 14:34:29 -07:00
Kelly Brazil
95672c23b7 doc update 2024-10-18 14:30:04 -07:00
Jose E. Rodriguez
a2e0e6d549 feat: Add Windows ipconfig parser to jc (#596)
* feat: Introduce ipconfig parser

* fix: add parsing support for "connection_specific_dns_suffix_search_list" and windows XP ipv4 addresses, remove dateutil dependency

* fix: introduce unit tests, correct import of datetime

* fix: changed preferred to status to account for other ip statuses, and parsed link local ipv6 prefix length

* fix: compress _parse_header_line and _parse_adapter_line + fix casing in unit test file

---------

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-10-18 14:17:14 -07:00
Kelly Brazil
2df5e79295 doc update 2024-10-18 09:56:32 -07:00
Jake Ob
c5e0642b0b Fix broken controller regexp scheme in the bluetoothctl parser (#599)
This commit fixes the controller parser scheme in order to take care
and extract some extra attributes, the manufacturer and version.
The order of the attributes appearing in the regexp scheme must follow
the order they appear in the `bluetoothctl show` outputs.

A new test has been added to test outputs with these extra attrs.

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-10-18 09:50:51 -07:00
Kelly Brazil
78150ded70 doc update 2024-10-17 14:05:06 -07:00
Kelly Brazil
05f3e4ea8a fixup tests 2024-10-17 13:53:20 -07:00
Kelly Brazil
b7cf0ca8d4 flip is_reserved to false for python ipv6 backports 2024-10-17 13:13:31 -07:00
Kelly Brazil
ba6e1e694e fix for python backports for ipv6 output 2024-10-17 13:06:17 -07:00
Kelly Brazil
aec3e3cd13 force github test 2024-10-17 09:15:29 -07:00
Kelly Brazil
d9363ae473 doc update 2024-09-26 09:49:06 -07:00
Kelly Brazil
0f367a435a formatting 2024-09-23 06:58:33 -07:00
Kelly Brazil
0955598b49 formatting 2024-09-23 06:55:17 -07:00
Kelly Brazil
f2a8e0087b add tests and doc update 2024-09-22 20:01:40 -07:00
Kelly Brazil
97e9798cef add posix_mode to utils/convert_size_to_int and use in df 2024-09-22 19:34:23 -07:00
Leonard Crestez
413519e02c #592: Handle df size suffixes as binary (#593)
* df: convert_size_to_int as binary

* tests: Update df -h expected output

---------

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-09-22 19:28:45 -07:00
Kelly Brazil
5c855e40c6 fix 6to4 test for python backports 2024-09-17 09:52:38 -07:00
Kelly Brazil
b66c5dbf55 doc update 2024-09-12 19:38:31 -07:00
Kelly Brazil
b054b1b782 doc update 2024-09-07 19:50:08 -07:00
Mabuchin
1593d0bf79 Add error type support for Linux Ping (#575)
* feat: add icmp error handle into linux_parse

* refactor: fixed timestamp offset logic(including error-response-type condition)
2024-09-07 19:46:43 -07:00
Kelly Brazil
8a22f8a468 add support for Link partner advertised link modes to ethtool parser 2024-09-07 18:43:41 -07:00
Kelly Brazil
c26f0641ff enhance ifconfig parser to support utun interfaces with assigned ipv4 addresses on macOS 2024-09-07 18:11:25 -07:00
Kelly Brazil
40eb2b7ef6 fix uptime for "user" instead of "users" 2024-09-07 17:24:21 -07:00
Kelly Brazil
71af0c5555 fix for IPv4 mapped IPv6 address changes in Python 3.13 2024-09-06 10:26:34 -07:00
Kelly Brazil
9f5532d91f formatting 2024-06-18 17:43:16 -07:00
Kelly Brazil
c68c919024 add mount test 2024-06-18 17:34:17 -07:00
Michał Górny
9eb4df34b1 adjust expected test_ip_address_ipv6_6to4 output for Python 3.12.4 (#573)
Adjust the expected output in `test_ip_address_ipv6_6to4` to account
for `is_global`/`is_private` changes for 6to4 addresses in Python 3.12.4
and Python 3.13.0.

Fixes #572

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
2024-06-18 08:53:19 -07:00
Kelly Brazil
921133f3ac version bump 2024-06-18 08:47:24 -07:00
Kelly Brazil
0350607359 fix for space if filesystem name 2024-06-18 08:47:02 -07:00
318 changed files with 17649 additions and 521 deletions

View File

@@ -9,12 +9,41 @@ on:
- "**/*.py"
jobs:
old_python:
very_old_python:
if: github.event.pull_request.draft == false
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-13, ubuntu-20.04, windows-2019]
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
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
@@ -37,6 +66,7 @@ jobs:
python -m unittest discover tests
latest_python:
if: github.event.pull_request.draft == false
runs-on: ${{ matrix.os }}
strategy:
matrix:

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ build/
.vscode/
_config.yml
.venv
.DS_Store

View File

@@ -1,8 +1,56 @@
jc changelog
202501012 v1.25.6
- Add `net-localgroup` Windows command parser
- Add `net-user` Windows command parser
- Add `route-print` Windows command parser
- Add `traceroute-s` streaming command parser
- Add `max_hops` and `data_bytes` fields to `traceroute` command parser
- Add `x509-crl` file parser to support Certificate Revocation List PEM and DER files
- Add `yay` as a magic command for the `pacman` command parser
- Fix `bluetoothctl` command parser to support output with the `cable_pairing` attribute
- Fix `nmcli` command parser to support blank `team.config` JSON value and `team-port.config` JSON value
- Fix `top` command parsers to correct memory size field parsing. Several new unit
and byte conversion fields have been added
- Fix `who` command parser to support the `process` field on Debian13
20250503 v1.25.5
- Add `amixer` command parser
- Enhance `iptables` command parser to add default policy statistics fields
- Fix `bluetoothctl` parser failing to parse controllers with power state prop
- Fix `lsblk` command parser to support multiple mountpoints. Also, added
byte conversions for size fields.
- Fix `nmcli` command parser to support `team.config` JSON field
- Fix `time` command parser for output that does not contain centiseconds
- Fix `x509-cert` parser to handle IDNA2008 encoded email addresses with a warning
- Fix typing for upcoming python v3.14
- Fix timezone setting for tests to support minimal chrooted builds
20241125 v1.25.4
- Add `ipconfig` command parser (`ipconfig` for Windows)
- Add `pacman` command parser
- Add `wg show` command parser
- Enhance `ethtool` parser to support `link_partner_advertised_link_modes`
- Enhance `ifconfig` parser to support `utun` interfaces with assigned IPv4 addresses on macOS
- Enhance `nsd-control` parser with additional state fields
- Enhance `ping-s` streaming parser to support error replies
- Fix `bluetoothctl` parser when extra attributes like `manufacturer` and `version` exist
- Fix `df` parser to correctly output binary vs. decimal size outputs
- Fix `ip-address` parser for Python 3.13 changes to IPv4 mapped IPv6 addresses
- Fix `iw-scan` parser to output more fields (still beta quality)
- Fix `mount` parser for cases where there are spaces in the filesystem name
- Fix `netstat` parser for cases where there are spaces in the program name
- Fix `pkg-index-deb`, `apt-cache-show`, and `rpm-qi` parsers to correctly convert contiguous packages with the same name
- Fix `traceroute` parser to support extreme IPv6 cases
- Fix `uptime` parser for data that contains `user` instead of `users`
- Fix `yaml` parser to support values that start with an equal sign
- Enhance `jc.utils.convert_size_to_int()` to add `posix_mode` and `decimal_bias` parameters
- Enhance cli to coerce any non-JSON-serializable objects to a string
20240609 v1.25.3
- Enhance `bluetoothctl` parser with added `battery_percentage` field
- Enhance `git-log` standard and streaming parsers with added `lines_changed` field under `file_stats`
- Fix `lspci` parser to handle `physlot` fields with a range value
- Fix `pci-ids` parser to correctly handle multiple subdevices
- Fix `pip-show` parser to handle multi-line fields with a beginning blank line
- Fix `ss` parser to correctly handle the `Recv-Q` field being too close to the `Status` field

View File

@@ -1,4 +1,3 @@
[![Tests](https://github.com/kellyjonbrazil/jc/workflows/Tests/badge.svg?branch=master)](https://github.com/kellyjonbrazil/jc/actions)
[![Pypi](https://img.shields.io/pypi/v/jc.svg)](https://pypi.org/project/jc/)
> Check out the `jc` Python [package documentation](https://github.com/kellyjonbrazil/jc/tree/master/docs) for developers
@@ -159,6 +158,7 @@ option.
| `--acpi` | `acpi` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/acpi) |
| `--airport` | `airport -I` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/airport) |
| `--airport-s` | `airport -s` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/airport_s) |
| `--amixer` | `amixer` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/amixer) |
| `--apt-cache-show` | `apt-cache show` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/apt_cache_show) |
| `--apt-get-sqq` | `apt-get -sqq` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/apt_get_sqq) |
| `--arp` | `arp` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/arp) |
@@ -218,6 +218,7 @@ option.
| `--iostat` | `iostat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iostat) |
| `--iostat-s` | `iostat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iostat_s) |
| `--ip-address` | IPv4 and IPv6 Address string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ip_address) |
| `--ipconfig` | `ipconfig` Windows command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ipconfig) |
| `--iptables` | `iptables` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iptables) |
| `--ip-route` | `ip route` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ip_route) |
| `--iw-scan` | `iw dev [device] scan` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iw_scan) |
@@ -244,12 +245,15 @@ option.
| `--mpstat-s` | `mpstat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mpstat_s) |
| `--needrestart` | `needrestart -b` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/needrestart) |
| `--netstat` | `netstat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/netstat) |
| `--net-localgroup` | `net localgroup` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/net_localgroup) |
| `--net-user` | `net user` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/net_user) |
| `--nmcli` | `nmcli` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/nmcli) |
| `--nsd-control` | `nsd-control` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/nsd_control) |
| `--ntpq` | `ntpq -p` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ntpq) |
| `--openvpn` | openvpn-status.log file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/openvpn) |
| `--os-prober` | `os-prober` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/os_prober) |
| `--os-release` | `/etc/os-release` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/os_release) |
| `--pacman` | `pacman` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pacman) |
| `--passwd` | `/etc/passwd` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/passwd) |
| `--path` | POSIX path string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/path) |
| `--path-list` | POSIX path list string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/path_list) |
@@ -269,6 +273,7 @@ option.
| `--ps` | `ps` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ps) |
| `--resolve-conf` | `/etc/resolve.conf` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/resolve_conf) |
| `--route` | `route` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/route) |
| `--route-print` | `route print` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/route_print) |
| `--rpm-qi` | `rpm -qi` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rpm_qi) |
| `--rsync` | `rsync` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rsync) |
| `--rsync-s` | `rsync` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rsync_s) |
@@ -300,6 +305,7 @@ option.
| `--top-s` | `top -b` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/top_s) |
| `--tracepath` | `tracepath` and `tracepath6` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/tracepath) |
| `--traceroute` | `traceroute` and `traceroute6` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/traceroute) |
| `--traceroute-s` | `traceroute` and `traceroute6` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/traceroute_s) |
| `--tune2fs` | `tune2fs -l` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/tune2fs) |
| `--udevadm` | `udevadm info` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/udevadm) |
| `--ufw` | `ufw status` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw) |
@@ -316,8 +322,10 @@ option.
| `--vmstat-s` | `vmstat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/vmstat_s) |
| `--w` | `w` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/w) |
| `--wc` | `wc` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/wc) |
| `--wg-show` | `wg show` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/wg_show) |
| `--who` | `who` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/who) |
| `--x509-cert` | X.509 PEM and DER certificate file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/x509_cert) |
| `--x509-crl` | X.509 PEM and DER certificate revocation list file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/x509_crl) |
| `--x509-csr` | X.509 PEM and DER certificate request file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/x509_csr) |
| `--xml` | XML file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/xml) |
| `--xrandr` | `xrandr` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/xrandr) |
@@ -1375,4 +1383,4 @@ cat istio.yaml | jc -p --yaml
]
```
© 2019-2024 Kelly Brazil
© 2019-2025 Kelly Brazil

View File

@@ -3,8 +3,8 @@ _jc()
local cur prev words cword jc_commands jc_parsers jc_options \
jc_about_options jc_about_mod_options jc_help_options jc_special_options
jc_commands=(acpi airport apt-cache apt-get arp blkid bluetoothctl cbt certbot chage cksum crontab curl date debconf-show df dig dmidecode dpkg du efibootmgr env ethtool file findmnt finger free git gpg hciconfig host id ifconfig iostat ip iptables iw iwconfig jobs last lastb ls lsattr lsb_release lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat needrestart netstat nmcli nsd-control ntpq os-prober pidstat ping ping6 pip pip3 postconf printenv ps route rpm rsync sfdisk sha1sum sha224sum sha256sum sha384sum sha512sum shasum ss ssh sshd stat sum swapon sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 tune2fs udevadm ufw uname update-alternatives upower uptime vdir veracrypt vmstat w wc who xrandr zipinfo zpool)
jc_parsers=(--acpi --airport --airport-s --apt-cache-show --apt-get-sqq --arp --asciitable --asciitable-m --blkid --bluetoothctl --cbt --cef --cef-s --certbot --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --curl-head --date --datetime-iso --debconf-show --df --dig --dir --dmidecode --dpkg-l --du --efibootmgr --email-address --env --ethtool --file --find --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --host --hosts --http-headers --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --iptables --ip-route --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --kv-dup --last --ls --ls-s --lsattr --lsb-release --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --needrestart --netstat --nmcli --nsd-control --ntpq --openvpn --os-prober --os-release --passwd --path --path-list --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --pkg-index-apk --pkg-index-deb --plist --postconf --proc --proc-buddyinfo --proc-cmdline --proc-consoles --proc-cpuinfo --proc-crypto --proc-devices --proc-diskstats --proc-filesystems --proc-interrupts --proc-iomem --proc-ioports --proc-loadavg --proc-locks --proc-meminfo --proc-modules --proc-mtrr --proc-pagetypeinfo --proc-partitions --proc-slabinfo --proc-softirqs --proc-stat --proc-swaps --proc-uptime --proc-version --proc-vmallocinfo --proc-vmstat --proc-zoneinfo --proc-driver-rtc --proc-net-arp --proc-net-dev --proc-net-dev-mcast --proc-net-if-inet6 --proc-net-igmp --proc-net-igmp6 --proc-net-ipv6-route --proc-net-netlink --proc-net-netstat --proc-net-packet --proc-net-protocols --proc-net-route --proc-net-tcp --proc-net-unix --proc-pid-fdinfo --proc-pid-io --proc-pid-maps --proc-pid-mountinfo --proc-pid-numa-maps --proc-pid-smaps --proc-pid-stat --proc-pid-statm --proc-pid-status --ps --resolve-conf --route --rpm-qi --rsync --rsync-s --semver --sfdisk --shadow --srt --ss --ssh-conf --sshd-conf --stat --stat-s --swapon --sysctl --syslog --syslog-s --syslog-bsd --syslog-bsd-s --systemctl --systemctl-lj --systemctl-ls --systemctl-luf --systeminfo --time --timedatectl --timestamp --toml --top --top-s --tracepath --traceroute --tune2fs --udevadm --ufw --ufw-appinfo --uname --update-alt-gs --update-alt-q --upower --uptime --url --ver --veracrypt --vmstat --vmstat-s --w --wc --who --x509-cert --x509-csr --xml --xrandr --yaml --zipinfo --zpool-iostat --zpool-status)
jc_commands=(acpi airport amixer apt-cache apt-get arp blkid bluetoothctl cbt certbot chage cksum crontab curl date debconf-show df dig dmidecode dpkg du efibootmgr env ethtool file findmnt finger free git gpg hciconfig host id ifconfig iostat ip ipconfig iptables iw iwconfig jobs last lastb ls lsattr lsb_release lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat needrestart net netstat nmcli nsd-control ntpq os-prober pacman pidstat ping ping6 pip pip3 postconf printenv ps route rpm rsync sfdisk sha1sum sha224sum sha256sum sha384sum sha512sum shasum ss ssh sshd stat sum swapon sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 tune2fs udevadm ufw uname update-alternatives upower uptime vdir veracrypt vmstat w wc wg who xrandr yay zipinfo zpool)
jc_parsers=(--acpi --airport --airport-s --amixer --apt-cache-show --apt-get-sqq --arp --asciitable --asciitable-m --blkid --bluetoothctl --cbt --cef --cef-s --certbot --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --curl-head --date --datetime-iso --debconf-show --df --dig --dir --dmidecode --dpkg-l --du --efibootmgr --email-address --env --ethtool --file --find --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --host --hosts --http-headers --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --ipconfig --iptables --ip-route --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --kv-dup --last --ls --ls-s --lsattr --lsb-release --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --needrestart --netstat --net-localgroup --net-user --nmcli --nsd-control --ntpq --openvpn --os-prober --os-release --pacman --passwd --path --path-list --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --pkg-index-apk --pkg-index-deb --plist --postconf --proc --proc-buddyinfo --proc-cmdline --proc-consoles --proc-cpuinfo --proc-crypto --proc-devices --proc-diskstats --proc-filesystems --proc-interrupts --proc-iomem --proc-ioports --proc-loadavg --proc-locks --proc-meminfo --proc-modules --proc-mtrr --proc-pagetypeinfo --proc-partitions --proc-slabinfo --proc-softirqs --proc-stat --proc-swaps --proc-uptime --proc-version --proc-vmallocinfo --proc-vmstat --proc-zoneinfo --proc-driver-rtc --proc-net-arp --proc-net-dev --proc-net-dev-mcast --proc-net-if-inet6 --proc-net-igmp --proc-net-igmp6 --proc-net-ipv6-route --proc-net-netlink --proc-net-netstat --proc-net-packet --proc-net-protocols --proc-net-route --proc-net-tcp --proc-net-unix --proc-pid-fdinfo --proc-pid-io --proc-pid-maps --proc-pid-mountinfo --proc-pid-numa-maps --proc-pid-smaps --proc-pid-stat --proc-pid-statm --proc-pid-status --ps --resolve-conf --route --route-print --rpm-qi --rsync --rsync-s --semver --sfdisk --shadow --srt --ss --ssh-conf --sshd-conf --stat --stat-s --swapon --sysctl --syslog --syslog-s --syslog-bsd --syslog-bsd-s --systemctl --systemctl-lj --systemctl-ls --systemctl-luf --systeminfo --time --timedatectl --timestamp --toml --top --top-s --tracepath --traceroute --traceroute-s --tune2fs --udevadm --ufw --ufw-appinfo --uname --update-alt-gs --update-alt-q --upower --uptime --url --ver --veracrypt --vmstat --vmstat-s --w --wc --wg-show --who --x509-cert --x509-crl --x509-csr --xml --xrandr --yaml --zipinfo --zpool-iostat --zpool-status)
jc_options=(--force-color -C --debug -d --monochrome -m --meta-out -M --pretty -p --quiet -q --raw -r --slurp -s --unbuffer -u --yaml-out -y)
jc_about_options=(--about -a)
jc_about_mod_options=(--pretty -p --yaml-out -y --monochrome -m --force-color -C)

View File

@@ -9,10 +9,11 @@ _jc() {
jc_help_options jc_help_options_describe \
jc_special_options jc_special_options_describe
jc_commands=(acpi airport apt-cache apt-get arp blkid bluetoothctl cbt certbot chage cksum crontab curl date debconf-show df dig dmidecode dpkg du efibootmgr env ethtool file findmnt finger free git gpg hciconfig host id ifconfig iostat ip iptables iw iwconfig jobs last lastb ls lsattr lsb_release lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat needrestart netstat nmcli nsd-control ntpq os-prober pidstat ping ping6 pip pip3 postconf printenv ps route rpm rsync sfdisk sha1sum sha224sum sha256sum sha384sum sha512sum shasum ss ssh sshd stat sum swapon sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 tune2fs udevadm ufw uname update-alternatives upower uptime vdir veracrypt vmstat w wc who xrandr zipinfo zpool)
jc_commands=(acpi airport amixer apt-cache apt-get arp blkid bluetoothctl cbt certbot chage cksum crontab curl date debconf-show df dig dmidecode dpkg du efibootmgr env ethtool file findmnt finger free git gpg hciconfig host id ifconfig iostat ip ipconfig iptables iw iwconfig jobs last lastb ls lsattr lsb_release lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat needrestart net netstat nmcli nsd-control ntpq os-prober pacman pidstat ping ping6 pip pip3 postconf printenv ps route rpm rsync sfdisk sha1sum sha224sum sha256sum sha384sum sha512sum shasum ss ssh sshd stat sum swapon sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 tune2fs udevadm ufw uname update-alternatives upower uptime vdir veracrypt vmstat w wc wg who xrandr yay zipinfo zpool)
jc_commands_describe=(
'acpi:run "acpi" command with magic syntax.'
'airport:run "airport" command with magic syntax.'
'amixer:run "amixer" command with magic syntax.'
'apt-cache:run "apt-cache" command with magic syntax.'
'apt-get:run "apt-get" command with magic syntax.'
'arp:run "arp" command with magic syntax.'
@@ -46,6 +47,7 @@ _jc() {
'ifconfig:run "ifconfig" command with magic syntax.'
'iostat:run "iostat" command with magic syntax.'
'ip:run "ip" command with magic syntax.'
'ipconfig:run "ipconfig" command with magic syntax.'
'iptables:run "iptables" command with magic syntax.'
'iw:run "iw" command with magic syntax.'
'iwconfig:run "iwconfig" command with magic syntax.'
@@ -66,11 +68,13 @@ _jc() {
'mount:run "mount" command with magic syntax.'
'mpstat:run "mpstat" command with magic syntax.'
'needrestart:run "needrestart" command with magic syntax.'
'net:run "net" command with magic syntax.'
'netstat:run "netstat" command with magic syntax.'
'nmcli:run "nmcli" command with magic syntax.'
'nsd-control:run "nsd-control" command with magic syntax.'
'ntpq:run "ntpq" command with magic syntax.'
'os-prober:run "os-prober" command with magic syntax.'
'pacman:run "pacman" command with magic syntax.'
'pidstat:run "pidstat" command with magic syntax.'
'ping:run "ping" command with magic syntax.'
'ping6:run "ping6" command with magic syntax.'
@@ -116,16 +120,19 @@ _jc() {
'vmstat:run "vmstat" command with magic syntax.'
'w:run "w" command with magic syntax.'
'wc:run "wc" command with magic syntax.'
'wg:run "wg" command with magic syntax.'
'who:run "who" command with magic syntax.'
'xrandr:run "xrandr" command with magic syntax.'
'yay:run "yay" command with magic syntax.'
'zipinfo:run "zipinfo" command with magic syntax.'
'zpool:run "zpool" command with magic syntax.'
)
jc_parsers=(--acpi --airport --airport-s --apt-cache-show --apt-get-sqq --arp --asciitable --asciitable-m --blkid --bluetoothctl --cbt --cef --cef-s --certbot --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --curl-head --date --datetime-iso --debconf-show --df --dig --dir --dmidecode --dpkg-l --du --efibootmgr --email-address --env --ethtool --file --find --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --host --hosts --http-headers --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --iptables --ip-route --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --kv-dup --last --ls --ls-s --lsattr --lsb-release --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --needrestart --netstat --nmcli --nsd-control --ntpq --openvpn --os-prober --os-release --passwd --path --path-list --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --pkg-index-apk --pkg-index-deb --plist --postconf --proc --proc-buddyinfo --proc-cmdline --proc-consoles --proc-cpuinfo --proc-crypto --proc-devices --proc-diskstats --proc-filesystems --proc-interrupts --proc-iomem --proc-ioports --proc-loadavg --proc-locks --proc-meminfo --proc-modules --proc-mtrr --proc-pagetypeinfo --proc-partitions --proc-slabinfo --proc-softirqs --proc-stat --proc-swaps --proc-uptime --proc-version --proc-vmallocinfo --proc-vmstat --proc-zoneinfo --proc-driver-rtc --proc-net-arp --proc-net-dev --proc-net-dev-mcast --proc-net-if-inet6 --proc-net-igmp --proc-net-igmp6 --proc-net-ipv6-route --proc-net-netlink --proc-net-netstat --proc-net-packet --proc-net-protocols --proc-net-route --proc-net-tcp --proc-net-unix --proc-pid-fdinfo --proc-pid-io --proc-pid-maps --proc-pid-mountinfo --proc-pid-numa-maps --proc-pid-smaps --proc-pid-stat --proc-pid-statm --proc-pid-status --ps --resolve-conf --route --rpm-qi --rsync --rsync-s --semver --sfdisk --shadow --srt --ss --ssh-conf --sshd-conf --stat --stat-s --swapon --sysctl --syslog --syslog-s --syslog-bsd --syslog-bsd-s --systemctl --systemctl-lj --systemctl-ls --systemctl-luf --systeminfo --time --timedatectl --timestamp --toml --top --top-s --tracepath --traceroute --tune2fs --udevadm --ufw --ufw-appinfo --uname --update-alt-gs --update-alt-q --upower --uptime --url --ver --veracrypt --vmstat --vmstat-s --w --wc --who --x509-cert --x509-csr --xml --xrandr --yaml --zipinfo --zpool-iostat --zpool-status)
jc_parsers=(--acpi --airport --airport-s --amixer --apt-cache-show --apt-get-sqq --arp --asciitable --asciitable-m --blkid --bluetoothctl --cbt --cef --cef-s --certbot --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --curl-head --date --datetime-iso --debconf-show --df --dig --dir --dmidecode --dpkg-l --du --efibootmgr --email-address --env --ethtool --file --find --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --host --hosts --http-headers --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --ipconfig --iptables --ip-route --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --kv-dup --last --ls --ls-s --lsattr --lsb-release --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --needrestart --netstat --net-localgroup --net-user --nmcli --nsd-control --ntpq --openvpn --os-prober --os-release --pacman --passwd --path --path-list --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --pkg-index-apk --pkg-index-deb --plist --postconf --proc --proc-buddyinfo --proc-cmdline --proc-consoles --proc-cpuinfo --proc-crypto --proc-devices --proc-diskstats --proc-filesystems --proc-interrupts --proc-iomem --proc-ioports --proc-loadavg --proc-locks --proc-meminfo --proc-modules --proc-mtrr --proc-pagetypeinfo --proc-partitions --proc-slabinfo --proc-softirqs --proc-stat --proc-swaps --proc-uptime --proc-version --proc-vmallocinfo --proc-vmstat --proc-zoneinfo --proc-driver-rtc --proc-net-arp --proc-net-dev --proc-net-dev-mcast --proc-net-if-inet6 --proc-net-igmp --proc-net-igmp6 --proc-net-ipv6-route --proc-net-netlink --proc-net-netstat --proc-net-packet --proc-net-protocols --proc-net-route --proc-net-tcp --proc-net-unix --proc-pid-fdinfo --proc-pid-io --proc-pid-maps --proc-pid-mountinfo --proc-pid-numa-maps --proc-pid-smaps --proc-pid-stat --proc-pid-statm --proc-pid-status --ps --resolve-conf --route --route-print --rpm-qi --rsync --rsync-s --semver --sfdisk --shadow --srt --ss --ssh-conf --sshd-conf --stat --stat-s --swapon --sysctl --syslog --syslog-s --syslog-bsd --syslog-bsd-s --systemctl --systemctl-lj --systemctl-ls --systemctl-luf --systeminfo --time --timedatectl --timestamp --toml --top --top-s --tracepath --traceroute --traceroute-s --tune2fs --udevadm --ufw --ufw-appinfo --uname --update-alt-gs --update-alt-q --upower --uptime --url --ver --veracrypt --vmstat --vmstat-s --w --wc --wg-show --who --x509-cert --x509-crl --x509-csr --xml --xrandr --yaml --zipinfo --zpool-iostat --zpool-status)
jc_parsers_describe=(
'--acpi:`acpi` command parser'
'--airport:`airport -I` command parser'
'--airport-s:`airport -s` command parser'
'--amixer:`amixer` command parser'
'--apt-cache-show:`apt-cache show` command parser'
'--apt-get-sqq:`apt-get -sqq` command parser'
'--arp:`arp` command parser'
@@ -185,6 +192,7 @@ _jc() {
'--iostat:`iostat` command parser'
'--iostat-s:`iostat` command streaming parser'
'--ip-address:IPv4 and IPv6 Address string parser'
'--ipconfig:`ipconfig` Windows command parser'
'--iptables:`iptables` command parser'
'--ip-route:`ip route` command parser'
'--iw-scan:`iw dev [device] scan` command parser'
@@ -211,12 +219,15 @@ _jc() {
'--mpstat-s:`mpstat` command streaming parser'
'--needrestart:`needrestart -b` command parser'
'--netstat:`netstat` command parser'
'--net-localgroup:`net localgroup` command parser'
'--net-user:`net user` command parser'
'--nmcli:`nmcli` command parser'
'--nsd-control:`nsd-control` command parser'
'--ntpq:`ntpq -p` command parser'
'--openvpn:openvpn-status.log file parser'
'--os-prober:`os-prober` command parser'
'--os-release:`/etc/os-release` file parser'
'--pacman:`pacman` command parser'
'--passwd:`/etc/passwd` file parser'
'--path:POSIX path string parser'
'--path-list:POSIX path list string parser'
@@ -287,6 +298,7 @@ _jc() {
'--ps:`ps` command parser'
'--resolve-conf:`/etc/resolve.conf` file parser'
'--route:`route` command parser'
'--route-print:`route print` command parser'
'--rpm-qi:`rpm -qi` command parser'
'--rsync:`rsync` command parser'
'--rsync-s:`rsync` command streaming parser'
@@ -318,6 +330,7 @@ _jc() {
'--top-s:`top -b` command streaming parser'
'--tracepath:`tracepath` and `tracepath6` command parser'
'--traceroute:`traceroute` and `traceroute6` command parser'
'--traceroute-s:`traceroute` and `traceroute6` command streaming parser'
'--tune2fs:`tune2fs -l` command parser'
'--udevadm:`udevadm info` command parser'
'--ufw:`ufw status` command parser'
@@ -334,8 +347,10 @@ _jc() {
'--vmstat-s:`vmstat` command streaming parser'
'--w:`w` command parser'
'--wc:`wc` command parser'
'--wg-show:`wg show` command parser'
'--who:`who` command parser'
'--x509-cert:X.509 PEM and DER certificate file parser'
'--x509-crl:X.509 PEM and DER certificate revocation list file parser'
'--x509-csr:X.509 PEM and DER certificate request file parser'
'--xml:XML file parser'
'--xrandr:`xrandr` command parser'

View File

@@ -36,7 +36,7 @@ returned.
Parameters:
documentation: (boolean) include parser docstrings if True
documentation: (boolean) include parser docstrings if `True`
show_hidden: (boolean) also show parsers marked as hidden
in their info metadata.
show_deprecated: (boolean) also show parsers marked as
@@ -172,17 +172,17 @@ Parameters:
variants of the module name.
A Module object can also be passed
directly or via get_parser()
directly or via `get_parser()`
data: (string or data to parse (string or bytes for
bytes or standard parsers, iterable of
iterable) strings for streaming parsers)
raw: (boolean) output preprocessed JSON if True
raw: (boolean) output preprocessed JSON if `True`
quiet: (boolean) suppress warning messages if True
quiet: (boolean) suppress warning messages if `True`
ignore_exceptions: (boolean) ignore parsing exceptions if True
ignore_exceptions: (boolean) ignore parsing exceptions if `True`
(streaming parsers only)
Returns:
@@ -209,7 +209,7 @@ Parameters:
variants of the module name as well
as a parser module object.
documentation: (boolean) include parser docstring if True
documentation: (boolean) include parser docstring if `True`
<a id="jc.lib.parser_mod_list"></a>

117
docs/parsers/amixer.md Normal file
View File

@@ -0,0 +1,117 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.amixer"></a>
# jc.parsers.amixer
jc - JSON Convert `amixer sget` command output parser
Usage (cli):
$ amixer sget <control_name> | jc --amixer
$ amixer sget Master | jc --amixer
$ amixer sget Capture | jc --amixer
$ amixer sget Speakers | jc --amixer
Usage (module):
import jc
result = jc.parse('amixer', <amixer_sget_command_output>)
Schema:
{
"control_name": string,
"capabilities": [
string
],
"playback_channels": [
string
],
"limits": {
"playback_min": integer,
"playback_max": integer
},
"mono": {
"playback_value": integer,
"percentage": integer,
"db": float,
"status": boolean
}
}
Examples:
$ amixer sget Master | jc --amixer -p
{
"control_name": "Capture",
"capabilities": [
"cvolume",
"cswitch"
],
"playback_channels": [],
"limits": {
"playback_min": 0,
"playback_max": 63
},
"front_left": {
"playback_value": 63,
"percentage": 100,
"db": 30.0,
"status": true
},
"front_right": {
"playback_value": 63,
"percentage": 100,
"db": 30.0,
"status": true
}
}
$ amixer sget Master | jc --amixer -p -r
{
"control_name": "Master",
"capabilities": [
"pvolume",
"pvolume-joined",
"pswitch",
"pswitch-joined"
],
"playback_channels": [
"Mono"
],
"limits": {
"playback_min": "0",
"playback_max": "87"
},
"mono": {
"playback_value": "87",
"percentage": "100%",
"db": "0.00db",
"status": "on"
}
}
<a id="jc.parsers.amixer.parse"></a>
### parse
```python
def parse(data: str, raw: bool = False, quiet: bool = False) -> Dict
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Dictionary. Raw or processed structured data.
### Parser Information
Compatibility: linux
Source: [`jc/parsers/amixer.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/amixer.py)
Version 1.0 by Eden Refael (edenraf@hotmail.com)

View File

@@ -33,6 +33,8 @@ a controller and a device but there might be fields corresponding to one entity.
Controller:
[
{
"manufacturer": string,
"version": string,
"name": string,
"is_default": boolean,
"is_public": boolean,
@@ -67,6 +69,7 @@ a controller and a device but there might be fields corresponding to one entity.
"blocked": string,
"connected": string,
"legacy_pairing": string,
"cable_pairing": string,
"rssi": int,
"txpower": int,
"uuids": array,
@@ -134,4 +137,4 @@ Compatibility: linux
Source: [`jc/parsers/bluetoothctl.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/bluetoothctl.py)
Version 1.2 by Jake Ob (iakopap at gmail.com)
Version 1.5 by Jake Ob (iakopap at gmail.com)

View File

@@ -124,4 +124,4 @@ Compatibility: linux, darwin, freebsd
Source: [`jc/parsers/df.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/df.py)
Version 2.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 2.1 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -194,4 +194,4 @@ Compatibility: linux
Source: [`jc/parsers/ethtool.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/ethtool.py)
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -242,4 +242,4 @@ Compatibility: linux, aix, freebsd, darwin
Source: [`jc/parsers/ifconfig.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/ifconfig.py)
Version 2.3 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 2.4 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -556,4 +556,4 @@ Source: [`jc/parsers/ip_address.py`](https://github.com/kellyjonbrazil/jc/blob/m
This parser can be used with the `--slurp` command-line option.
Version 1.4 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.5 by Kelly Brazil (kellyjonbrazil@gmail.com)

461
docs/parsers/ipconfig.md Normal file
View File

@@ -0,0 +1,461 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.ipconfig"></a>
# jc.parsers.ipconfig
jc - JSON Convert `ipconfig` Windows command output parser
Usage (cli):
$ ipconfig /all | jc --ipconfig
$ ipconfig | jc --ipconfig
$ jc ipconfig /all
Usage (module):
import jc
result = jc.parse('ipconfig', ipconfig_command_output)
Schema:
{
"host_name": string,
"primary_dns_suffix": string,
"node_type": string,
"ip_routing_enabled": boolean,
"wins_proxy_enabled": boolean,
"dns_suffix_search_list": [
string
],
"adapters": [
{
"name_long": string,
"name": string,
"type": string,
"connection_specific_dns_suffix": string,
"connection_specific_dns_suffix_search_list": [
string
]
"description": string,
"physical_address": string,
"dhcp_enabled": boolean,
"autoconfiguration_enabled": boolean,
"ipv6_addresses": [
{
"address": string,
"status": string,
},
],
"temporary_ipv6_addresses": [
{
"address": string,
"status": string,
},
],
"link_local_ipv6_addresses": [
{
"address": string,
"status": string,
"prefix_length": integer,
}
],
"ipv4_addresses": [
{
"address": string, # [2]
"subnet_mask": string,
"status": string,
"autoconfigured": boolean # [1]
}
],
"default_gateways": [
string
],
"dhcp_server": null,
"dhcpv6_iaid": string,
"dhcpv6_client_duid": string,
"dns_servers": [
string
],
"primary_wins_server": string,
"lease_expires": string,
"lease_expires_epoch": integer, # [0]
"lease_expires_iso": string,
"lease_obtained": string,
"lease_obtained_epoch": integer, # [0]
"lease_obtained_iso": string,
"netbios_over_tcpip": boolean,
"media_state": string,
"extras": [
<string>: string
]
}
],
"extras": []
}
Notes:
[0] - The epoch calculated timestamp field is naive. (i.e. based on
the local time of the system the parser is run on)
[1] - 'autoconfigured' under 'ipv4_address' is only providing
indication if the ipv4 address was labeled as "Autoconfiguration
IPv4 Address" vs "IPv4 Address". It does not infer any
information from other fields
[2] - Windows XP uses 'IP Address' instead of 'IPv4 Address'. Both
values are parsed to the 'ipv4_address' object for consistency
Examples:
$ ipconfig /all | jc --ipconfig -p
{
"host_name": "DESKTOP-WIN11-HOME",
"primary_dns_suffix": null,
"node_type": "Hybrid",
"ip_routing_enabled": false,
"wins_proxy_enabled": false,
"dns_suffix_search_list": [
"localdomain"
],
"adapters": [
{
"name_long": "Ethernet adapter Ethernet",
"name": "Ethernet",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Intel(R) I211 Gigabit Network Connection",
"physical_address": "24-4B-FE-AB-43-C3",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Ethernet adapter Ethernet 2",
"name": "Ethernet 2",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Realtek PCIe 2.5GbE Family Controller",
"physical_address": "24-4B-FE-57-3D-F2",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Unknown adapter OpenVPN Data Channel Offload for NordVPN",
"name": "OpenVPN Data Channel Offload for NordVPN",
"type": "Unknown",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "OpenVPN Data Channel Offload",
"physical_address": null,
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Unknown adapter Local Area Connection",
"name": "Local Area Connection",
"type": "Unknown",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "TAP-NordVPN Windows Adapter V9",
"physical_address": "00-FF-4C-F4-5E-49",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Wireless LAN adapter Local Area Connection* 1",
"name": "Local Area Connection* 1",
"type": "Wireless LAN",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Microsoft Wi-Fi Direct Virtual Adapter",
"physical_address": "A8-7E-EA-5A-7F-DE",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Wireless LAN adapter Local Area Connection* 2",
"name": "Local Area Connection* 2",
"type": "Wireless LAN",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Microsoft Wi-Fi Direct Virtual Adapter #2",
"physical_address": "AA-7E-EA-F3-64-C3",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Ethernet adapter VMware Network Adapter VMnet1",
"name": "VMware Network Adapter VMnet1",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "VMware Virtual Ethernet Adapter for VMnet1",
"physical_address": "00-50-56-CC-27-73",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [
{
"address": "fe80::f47d:9c7f:69dc:591e",
"prefix_length": 8,
"status": "Preferred"
}
],
"ipv4_addresses": [
{
"address": "192.168.181.1",
"subnet_mask": "255.255.255.0",
"status": "Preferred",
"autoconfigured": false
}
],
"default_gateways": [],
"dhcp_server": "192.168.181.254",
"dhcpv6_iaid": "771772502",
"dhcpv6_client_duid": "00-01-00-01-2C-CF-19-EB-24-4B-FE-5B-9B-E6",
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": "2024-09-19T18:01:29",
"lease_obtained": "2024-09-19T08:31:29",
"netbios_over_tcpip": true,
"media_state": null,
"extras": []
},
{
"name_long": "Ethernet adapter VMware Network Adapter VMnet8",
"name": "VMware Network Adapter VMnet8",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "VMware Virtual Ethernet Adapter for VMnet8",
"physical_address": "00-50-56-C9-A3-78",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [
{
"address": "fe80::4551:bf0d:59dd:a4f0",
"prefix_length": 10,
"status": "Preferred"
}
],
"ipv4_addresses": [
{
"address": "192.168.213.1",
"subnet_mask": "255.255.255.0",
"status": "Preferred",
"autoconfigured": false
}
],
"default_gateways": [],
"dhcp_server": "192.168.213.254",
"dhcpv6_iaid": "788549718",
"dhcpv6_client_duid": "00-01-00-01-2C-CF-19-EB-24-4B-FE-5B-9B-E6",
"dns_servers": [],
"primary_wins_server": "192.168.213.2",
"lease_expires": "2024-09-19T18:01:29",
"lease_obtained": "2024-09-19T08:31:29",
"netbios_over_tcpip": true,
"media_state": null,
"extras": []
},
{
"name_long": "Wireless LAN adapter Wi-Fi",
"name": "Wi-Fi",
"type": "Wireless LAN",
"connection_specific_dns_suffix": "localdomain",
"connection_specific_dns_suffix_search_list": [],
"description": "Intel(R) Wi-Fi 6 AX200 160MHz",
"physical_address": "A8-7E-EA-55-26-B0",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [
{
"address": "fd63:cc9c:65eb:3f95:57c2:aa:10d8:db08",
"status": "Preferred"
}
],
"temporary_ipv6_addresses": [
{
"address": "fd63:cc9c:65eb:3f95:8928:348e:d692:b7ef",
"status": "Preferred"
}
],
"link_local_ipv6_addresses": [
{
"address": "fe80::4fae:1380:5a1b:8b6b",
"prefix_length": 11,
"status": "Preferred"
}
],
"ipv4_addresses": [
{
"address": "192.168.1.169",
"subnet_mask": "255.255.255.0",
"status": "Preferred",
"autoconfigured": false
}
],
"default_gateways": [
"192.168.1.1"
],
"dhcp_server": "192.168.1.1",
"dhcpv6_iaid": "162037482",
"dhcpv6_client_duid": "00-01-00-01-2C-CF-19-EB-24-4B-FE-5B-9B-E6",
"dns_servers": [
"192.168.1.1"
],
"primary_wins_server": null,
"lease_expires": "2024-09-20T08:31:30",
"lease_obtained": "2024-09-19T08:31:30",
"netbios_over_tcpip": true,
"media_state": null,
"extras": []
},
{
"name_long": "Ethernet adapter Bluetooth Network Connection",
"name": "Bluetooth Network Connection",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Bluetooth Device (Personal Area Network)",
"physical_address": "A8-7E-EA-43-23-14",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
}
],
"extras": []
}
<a id="jc.parsers.ipconfig.parse"></a>
### parse
```python
def parse(data, raw=False, quiet=False)
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Parsed dictionary. The raw and processed data structures are the same.
### Parser Information
Compatibility: windows
Source: [`jc/parsers/ipconfig.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/ipconfig.py)
Version 1.0 by joehacksalot (joehacksalot@gmail.com)

View File

@@ -25,6 +25,9 @@ Schema:
[
{
"chain": string,
"default_policy": string,
"default_packets": integer,
"default_bytes": integer,
"rules": [
{
"num" integer,
@@ -49,6 +52,9 @@ Examples:
[
{
"chain": "PREROUTING",
"default_policy": "DROP",
"default_packets": 0,
"default_bytes": 0,
"rules": [
{
"num": 1,
@@ -108,6 +114,9 @@ Examples:
[
{
"chain": "PREROUTING",
"default_policy": "DROP",
"default_packets": "0",
"default_bytes": "0",
"rules": [
{
"num": "1",
@@ -188,4 +197,4 @@ Compatibility: linux
Source: [`jc/parsers/iptables.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/iptables.py)
Version 1.11 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.12 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -146,4 +146,4 @@ Compatibility: linux
Source: [`jc/parsers/iw_scan.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/iw_scan.py)
Version 0.7 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 0.75 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -22,46 +22,53 @@ Schema:
[
{
"name": string,
"maj_min": string,
"rm": boolean,
"size": string,
"ro": boolean,
"type": string,
"mountpoint": string,
"kname": string,
"fstype": string,
"label": string,
"uuid": string,
"partlabel": string,
"partuuid": string,
"ra": integer,
"model": string,
"serial": string,
"state": string,
"owner": string,
"group": string,
"mode": string,
"alignment": integer,
"min_io": integer,
"opt_io": integer,
"phy_sec": integer,
"log_sec": integer,
"rota": boolean,
"sched": string,
"rq_size": integer,
"disc_aln": integer,
"disc_gran": string,
"disc_max": string,
"disc_zero": boolean,
"wsame": string,
"wwn": string,
"rand": boolean,
"pkname": string,
"hctl": string,
"tran": string,
"rev": string,
"vendor": string
"name": string,
"maj_min": string,
"rm": boolean,
"size": string,
"size_bytes": integer
"ro": boolean,
"type": string,
"mountpoint": string,
"mountpoints": [
string
],
"kname": string,
"fstype": string,
"label": string,
"uuid": string,
"partlabel": string,
"partuuid": string,
"ra": integer,
"model": string,
"serial": string,
"state": string,
"owner": string,
"group": string,
"mode": string,
"alignment": integer,
"min_io": integer,
"opt_io": integer,
"phy_sec": integer,
"log_sec": integer,
"rota": boolean,
"sched": string,
"rq_size": integer,
"disc_aln": integer,
"disc_gran": string,
"disc_gran_bytes": integer,
"disc_max": string,
"disc_max_bytes": integer,
"disc_zero": boolean,
"wsame": string,
"wsame_bytes": integer,
"wwn": string,
"rand": boolean,
"pkname": string,
"hctl": string,
"tran": string,
"rev": string,
"vendor": string
}
]
@@ -74,6 +81,7 @@ Examples:
"maj_min": "8:0",
"rm": false,
"size": "20G",
"size_bytes": 20000000000,
"ro": false,
"type": "disk",
"mountpoint": null
@@ -83,6 +91,7 @@ Examples:
"maj_min": "8:1",
"rm": false,
"size": "1G",
"size_bytes": 1000000000
"ro": false,
"type": "part",
"mountpoint": "/boot"
@@ -100,6 +109,7 @@ Examples:
"maj_min": "8:0",
"rm": false,
"size": "20G",
"size_bytes": 20000000000,
"ro": false,
"type": "disk",
"mountpoint": null,
@@ -126,9 +136,12 @@ Examples:
"rq_size": 128,
"disc_aln": 0,
"disc_gran": "0B",
"disc_gran_bytes": 0,
"disc_max": "0B",
"disc_max_bytes": 0,
"disc_zero": false,
"wsame": "32M",
"wsame_bytes": 32000000,
"wwn": null,
"rand": true,
"pkname": null,
@@ -142,6 +155,7 @@ Examples:
"maj_min": "8:1",
"rm": false,
"size": "1G",
"size_bytes": 1000000000
"ro": false,
"type": "part",
"mountpoint": "/boot",
@@ -168,9 +182,12 @@ Examples:
"rq_size": 128,
"disc_aln": 0,
"disc_gran": "0B",
"disc_gran_bytes": 0,
"disc_max": "0B",
"disc_max_bytes": 0,
"disc_zero": false,
"wsame": "32M",
"wsame_bytes": 32000000,
"wwn": null,
"rand": true,
"pkname": "sda",
@@ -192,6 +209,7 @@ Examples:
"maj_min": "8:0",
"rm": "0",
"size": "20G",
"size_bytes": 20000000000,
"ro": "0",
"type": "disk",
"mountpoint": null,
@@ -218,9 +236,12 @@ Examples:
"rq_size": "128",
"disc_aln": "0",
"disc_gran": "0B",
"disc_gran_bytes": 0,
"disc_max": "0B",
"disc_max_bytes": 0,
"disc_zero": "0",
"wsame": "32M",
"wsame_bytes": 32000000,
"wwn": null,
"rand": "1",
"pkname": null,
@@ -234,6 +255,7 @@ Examples:
"maj_min": "8:1",
"rm": "0",
"size": "1G",
"size_bytes": 1000000000
"ro": "0",
"type": "part",
"mountpoint": "/boot",
@@ -260,9 +282,12 @@ Examples:
"rq_size": "128",
"disc_aln": "0",
"disc_gran": "0B",
"disc_gran_bytes": 0,
"disc_max": "0B",
"disc_max_bytes": 0,
"disc_zero": "0",
"wsame": "32M",
"wsame_bytes": 32000000,
"wwn": null,
"rand": "1",
"pkname": "sda",
@@ -299,4 +324,4 @@ Compatibility: linux
Source: [`jc/parsers/lsblk.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/lsblk.py)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.10 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -100,4 +100,4 @@ Compatibility: linux, darwin, freebsd, aix
Source: [`jc/parsers/mount.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/mount.py)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.11 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -0,0 +1,81 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.net_localgroup"></a>
# jc.parsers.net_localgroup
jc - JSON Convert `net localgroup` command output parser
Usage (cli):
$ net localgroup | jc --net-localgroup
$ net localgroup /domain | jc --net-localgroup
$ net localgroup Administrators | jc --net-localgroup
$ net localgroup Administrators /domain | jc --net-localgroup
Usage (module):
import jc
result = jc.parse('net_localgroup', net_localgroup_command_output)
Schema:
{
"account_origin": string,
"domain": string,
"comment": string,
"groups": [
{
"name": string
"members": [
string
]
}
],
}
Examples:
$ net localgroup | jc --net-localgroup -p
{
"account_origin": null,
"comment": null,
"domain": null,
"groups": [
{
"name": "Administrators",
"members": [
"Administrator",
"Operator",
"ansible",
"user1"
]
}
]
}
<a id="jc.parsers.net_localgroup.parse"></a>
### parse
```python
def parse(data, raw=False, quiet=False)
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Parsed dictionary. The raw and processed data structures are the same.
### Parser Information
Compatibility: win32
Source: [`jc/parsers/net_localgroup.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/net_localgroup.py)
Version 1.0 by joehacksalot (joehacksalot@gmail.com)

212
docs/parsers/net_user.md Normal file
View File

@@ -0,0 +1,212 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.net_user"></a>
# jc.parsers.net_user
jc - JSON Convert `net user` command output parser
Usage (cli):
$ net users | jc --net-user
$ net users /domain | jc --net-user
$ net users User1 | jc --net-user
$ net users User1 /domain | jc --net-user
Usage (module):
import jc
result = jc.parse('net_user', net_user_command_output)
Schema:
{
"domain": string,
"account_origin": string,
"user_accounts": [
{
"user_name": string,
"full_name": string,
"comment": string,
"user_comment": string,
"country_region_code": string,
"account_active": boolean,
"account_expires": string,
"password_last_set": string,
"password_expires": string,
"password_changeable": string,
"password_required": boolean,
"user_may_change_password": boolean,
"workstations_allowed": string,
"logon_script": string,
"user_profile": string,
"home_directory": string,
"last_logon": string,
"logon_hours_allowed": string,
"local_group_memberships": [
string,
],
"global_group_memberships": [
string,
]
}
]
}
Examples:
$ net users | jc --net-user -p
{
"account_origin": "\\\\WIN-SERVER16",
"domain": "",
"user_accounts": [
{
"user_name": "Administrator"
},
{
"user_name": "DefaultAccount"
},
{
"user_name": "Guest"
},
{
"user_name": "pentera_BnlLQVnd7p"
},
{
"user_name": "user1"
}
]
}
$ net users /domain | jc --net-user -p
{
"account_origin": "\\\\DESKTOP-WIN10-PRO.somecompany.corp",
"domain": "somecompany.corp",
"user_accounts": [
{
"user_name": "aaron"
},
{
"user_name": "addison"
},
{
"user_name": "Administrator"
},
{
"user_name": "ansible"
},
{
"user_name": "da"
},
{
"user_name": "DefaultAccount"
},
{
"user_name": "Guest"
},
{
"user_name": "harrison"
},
{
"user_name": "james"
},
{
"user_name": "krbtgt"
},
{
"user_name": "liam"
},
{
"user_name": "localadmin"
},
{
"user_name": "tiffany"
}
]
}
$ net users Administrator | jc --net-user -p
{
"domain": "",
"user_accounts": [
{
"account_active": true,
"account_expires": "Never",
"comment": "Built-in account for administering the computer/domain",
"country_region_code": "000 (System Default)",
"global_group_memberships": [],
"last_logon": "2024-08-23T13:47:11",
"local_group_memberships": [
"Administrators"
],
"logon_hours_allowed": "All",
"password_changeable": "2021-12-17T11:07:14",
"password_expires": "2022-01-27T11:07:14",
"password_last_set": "2021-12-16T11:07:14",
"password_required": true,
"user_may_change_password": true,
"user_name": "Administrators",
"workstations_allowed": "All"
}
]
}
$ net users Administrator /domain | jc --net-user -p | jq
{
"domain": "somecompany.corp",
"user_accounts": [
{
"account_active": true,
"account_expires": "Never",
"comment": "Built-in account for administering the computer/domain",
"country_region_code": "000 (System Default)",
"global_group_memberships": [
"Domain Admins",
"Domain Users",
"Group Policy Creator",
"Enterprise Admins",
"Schema Admins"
],
"last_logon": "2024-07-17T13:46:12",
"local_group_memberships": [
"Administrators"
],
"logon_hours_allowed": "All",
"password_changeable": "2023-09-30T11:44:26",
"password_expires": "Never",
"password_last_set": "2023-09-29T11:44:26",
"password_required": true,
"user_may_change_password": true,
"user_name": "Administrators",
"workstations_allowed": "All"
}
]
}
<a id="jc.parsers.net_user.parse"></a>
### parse
```python
def parse(data, raw=False, quiet=False)
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Parsed dictionary. The raw and processed data structures are the same.
### Parser Information
Compatibility: win32
Source: [`jc/parsers/net_user.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/net_user.py)
Version 1.0 by joehacksalot (joehacksalot@gmail.com)

View File

@@ -380,4 +380,4 @@ Compatibility: linux, darwin, freebsd, win32
Source: [`jc/parsers/netstat.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/netstat.py)
Version 1.15 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.16 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -41,6 +41,8 @@ These are documented below.
[
{
"<key>": string/integer/float, # [0]
"team_config": object/null,
"team_port_config": object/null,
"dhcp4_option_x": {
"name": string,
"value": string/integer/float,
@@ -170,4 +172,4 @@ Compatibility: linux
Source: [`jc/parsers/nmcli.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/nmcli.py)
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -22,20 +22,24 @@ Schema:
[
{
"version": string,
"verbosity": integer,
"ratelimit": integer
"version": string,
"verbosity": integer,
"ratelimit": integer
}
]
[
{
"zone": string
"zone": string
"status": {
"state": string,
"served-serial": string,
"commit-serial": string,
"wait": string
"state": string,
"pattern": string, # Additional
"catalog-member-id": string, # Additional
"served-serial": string,
"commit-serial": string,
"notified-serial": string, # Conditional
"wait": string,
"transfer": string # Conditional
}
}
]
@@ -89,4 +93,4 @@ Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Source: [`jc/parsers/nsd_control.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/nsd_control.py)
Version 1.1 by Pettai (pettai@sunet.se)
Version 1.2 by Pettai (pettai@sunet.se)

208
docs/parsers/pacman.md Normal file
View File

@@ -0,0 +1,208 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.pacman"></a>
# jc.parsers.pacman
jc - JSON Convert `pacman` command output parser
Supports the following `pacman` arguments:
- `-Si`
- `-Sii`
- `-Qi`
- `-Qii`
The `*_epoch` calculated timestamp fields are naive. (i.e. based on the
local time of the system the parser is run on)
Usage (cli):
$ pacman -Si <package> | jc --pacman
or
$ jc pacman -Si <package>
Usage (module):
import jc
result = jc.parse('pacman', pacman_command_output)
Schema:
[
{
"repository": string,
"name": string,
"version": string,
"description": string,
"architecture": string,
"url": string,
"licenses": [
string
],
"groups": [
string
],
"provides": [
string
],
"depends_on": [
string
],
"optional_deps": [
{
"name": string,
"description": string
}
],
"optional_for": [
string
],
"conflicts_with": [
string
],
"replaces": [
string
],
"download_size": string,
"download_size_bytes": integer [0]
"installed_size": string,
"installed_size_bytes": integer, [0]
"packager": string,
"build_date": string,
"build_date_epoch": integer, [0]
"install_date": string,
"install_date_epoch": integer, [0]
"validated_by": [
string
],
"backup_files": [
string
]
}
]
[0] Field exists if conversion successful
Examples:
$ pacman -qii zstd | jc --pacman -p
[
{
"name": "zstd",
"version": "1.5.6-1",
"description": "Zstandard - Fast real-time compression algorithm",
"architecture": "x86_64",
"url": "https://facebook.github.io/zstd/",
"licenses": [
"BSD-3-Clause",
"GPL-2.0-only"
],
"groups": [],
"provides": [
"libzstd.so=1-64"
],
"depends_on": [
"glibc",
"gcc-libs",
"zlib",
"xz",
"lz4"
],
"required_by": [
"android-tools",
"appstream",
...
"tiled",
"vulkan-radeon",
"wireshark-cli"
],
"optional_for": [
"xarchiver"
],
"conflicts_with": [],
"replaces": [],
"installed_size": "1527.00 KiB",
"installed_size_bytes": 1563648,
"packager": "Levente Polyak <anthraxx@archlinux.org>",
"build_date": "Sat 11 May 2024 06:14:19 AM +08",
"build_date_epoch": 1715433259,
"install_date": "Fri 24 May 2024 09:50:31 AM +08",
"install_date_epoch": 1715663342,
"install_reason": "Installed as a dependency for another package",
"install_script": "No",
"validated_by": [
"Signature"
],
"extended_data": "pkgtype=pkg"
}
]
$ pacman -qii zstd | jc --pacman -p -r
[
{
"name": "zstd",
"version": "1.5.6-1",
"description": "Zstandard - Fast real-time compression algorithm",
"architecture": "x86_64",
"url": "https://facebook.github.io/zstd/",
"licenses": "BSD-3-Clause GPL-2.0-only",
"groups": null,
"provides": "libzstd.so=1-64",
"depends_on": "glibc gcc-libs zlib xz lz4",
"required_by": [
"android-tools appstream avr-gcc binutils blender blosc",
"boost-libs btrfs-progs cloudflare-warp-bin comgr curl",
"dolphin-emu file flatpak gcc gdal gnutls karchive",
"karchive5 kmod lib32-zstd libarchive libelf libtiff",
"libva-mesa-driver libxmlb libzip lld llvm-libs mariadb-libs",
"mesa mesa-vdpau minizip-ng mkinitcpio mold netcdf",
"opencl-clover-mesa opencl-rusticl-mesa openucx postgresql",
"postgresql-libs ppsspp qemu-img qemu-system-riscv",
"qemu-system-x86 qgis qt6-base qt6-tools rsync rustup",
"squashfs-tools squashfuse systemd-libs tiled vulkan-radeon",
"wireshark-cli"
],
"optional_for": "xarchiver",
"conflicts_with": null,
"replaces": null,
"installed_size": "1527.00 KiB",
"packager": "Levente Polyak <anthraxx@archlinux.org>",
"build_date": "Sat 11 May 2024 06:14:19 AM +08",
"install_date": "Fri 24 May 2024 09:50:31 AM +08",
"install_reason": "Installed as a dependency for another package",
"install_script": "No",
"validated_by": "Signature",
"extended_data": "pkgtype=pkg"
}
]
<a id="jc.parsers.pacman.parse"></a>
### parse
```python
def parse(data: str,
raw: bool = False,
quiet: bool = False) -> List[Dict[str, Any]]
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Source: [`jc/parsers/pacman.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/pacman.py)
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -109,4 +109,4 @@ Compatibility: linux, darwin, freebsd
Source: [`jc/parsers/ping_s.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/ping_s.py)
Version 1.5 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.6 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -31,7 +31,7 @@ or
or
$ cat /proc/meminfo | jc --proc-memifno
$ cat /proc/meminfo | jc --proc-meminfo
Usage (module):

204
docs/parsers/route_print.md Normal file
View File

@@ -0,0 +1,204 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.route_print"></a>
# jc.parsers.route_print
jc - JSON Convert `route print` command output parser
See also: the `route` command parser
Usage (cli):
$ route print | jc --route-print
Usage (module):
import jc
result = jc.parse('route_print', route_print_command_output)
Schema:
{
"interface_list": [
{
"interface_index": integer,
"mac_address": string,
"description": string
}
],
"ipv4_route_table": {
"active_routes": [
{
"network_destination": string,
"netmask": string,
"gateway": string,
"interface": string,
"metric": integer, # [0]
"metric_set_to_default": boolean # [1]
}
],
"persistent_routes": [
{
"network_address": string,
"netmask": string,
"gateway_address": string,
"metric": integer # [0]
"metric_set_to_default": boolean # [1]
}
]
},
"ipv6_route_table": {
"active_routes": [
{
"interface": integer,
"metric": integer, # [0]
"metric_set_to_default": boolean, # [1]
"network_destination": string,
"gateway": string
}
],
"persistent_routes": [
{
"interface": integer,
"metric": integer, # [0]
"metric_set_to_default": boolean, # [1]
"network_destination": string,
"gateway": string
}
]
}
}
[0] Null/None if "metric" = "Default"
[1] True if "metric" = "Default"
Examples:
$ route print | jc --route-print -p
{
"interface_list": [
{
"interface_index": 28,
"mac_address": null,
"description": "Tailscale Tunnel"
},
{
"interface_index": 12,
"mac_address": "00:1c:42:da:01:6a",
"description": "Parallels VirtIO Ethernet Adapter"
},
{
"interface_index": 1,
"mac_address": null,
"description": "Software Loopback Interface 1"
}
],
"ipv4_route_table": {
"active_routes": [
{
"network_destination": "0.0.0.0",
"netmask": "0.0.0.0",
"gateway": "10.211.55.1",
"interface": "10.211.55.3",
"metric": 15,
"metric_set_to_default": false
},
{
"network_destination": "10.0.0.0",
"netmask": "255.0.0.0",
"gateway": "192.168.22.1",
"interface": "10.211.55.3",
"metric": 16,
"metric_set_to_default": false
},
...
{
"network_destination": "255.255.255.255",
"netmask": "255.255.255.255",
"gateway": "On-link",
"interface": "10.211.55.3",
"metric": null,
"metric_set_to_default": true
}
],
"persistent_routes": [
{
"network_address": "10.0.1.0",
"netmask": "255.255.255.0",
"gateway_address": "192.168.22.1",
"metric": 1,
"metric_set_to_default": false
},
{
"network_address": "10.0.3.0",
"netmask": "255.255.255.0",
"gateway_address": "192.168.22.1",
"metric": 1,
"metric_set_to_default": false
},
...
]
},
"ipv6_route_table": {
"active_routes": [
{
"interface": 1,
"metric": 331,
"network_destination": "::1/128",
"gateway": "On-link",
"metric_set_to_default": false
},
{
"interface": 12,
"metric": 271,
"network_destination": "2001:db8::/64",
"gateway": "fe80::1",
"metric_set_to_default": false
},
...
{
"interface": 12,
"metric": 271,
"network_destination": "ff00::/8",
"gateway": "On-link",
"metric_set_to_default": false
}
],
"persistent_routes": [
{
"interface": 0,
"metric": 4294967295,
"network_destination": "2001:db8::/64",
"gateway": "fe80::1",
"metric_set_to_default": false
}
]
}
}
<a id="jc.parsers.route_print.parse"></a>
### parse
```python
def parse(data, raw=False, quiet=False)
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Parsed dictionary. The raw and processed data structures are the same.
### Parser Information
Compatibility: win32
Source: [`jc/parsers/route_print.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/route_print.py)
Version 1.0 by joehacksalot (joehacksalot@gmail.com)

View File

@@ -210,4 +210,4 @@ Compatibility: linux
Source: [`jc/parsers/rpm_qi.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/rpm_qi.py)
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -157,4 +157,4 @@ Compatibility: linux, darwin, cygwin, aix, freebsd
Source: [`jc/parsers/time.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/time.py)
Version 1.4 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.5 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -50,23 +50,31 @@ All `-` values are converted to `null`
"cpu_hardware": float,
"cpu_software": float,
"cpu_steal": float,
"mem_total": float, # [0]
"mem_free": float, # [0]
"mem_used": float, # [0]
"mem_buff_cache": float, # [0]
"swap_total": float, # [0]
"swap_free": float, # [0]
"swap_used": float, # [0]
"mem_available": float, # [0]
"mem_unit": string,
"mem_total": float,
"mem_free": float,
"mem_used": float,
"mem_buff_cache": float,
"swap_unit": string,
"swap_total": float,
"swap_free": float,
"swap_used": float,
"mem_available": float,
"processes": [
{
"pid": integer,
"user": string,
"priority": integer,
"nice": integer,
"virtual_mem": float, # [1]
"resident_mem": float, # [1]
"shared_mem": float, # [1]
"virtual_mem": float,
"virtual_mem_bytes": integer,
"virtual_mem_unit": string,
"resident_mem": float,
"resident_mem_bytes": integer,
"resident_mem_unit": string,
"shared_mem": float,
"shared_mem_bytes": integer,
"shared_mem_unit": string,
"status": string,
"percent_cpu": float,
"percent_mem": float,
@@ -87,9 +95,15 @@ All `-` values are converted to `null`
"thread_count": integer,
"last_used_processor": integer,
"time": string,
"swap": float, # [1]
"code": float, # [1]
"data": float, # [1]
"swap": float,
"swap_bytes": integer,
"swap_unit": string,
"code": float,
"code_bytes": integer,
"code_unit": string,
"data": float,
"data_bytes": integer,
"data_unit": string,
"major_page_fault_count": integer,
"minor_page_fault_count": integer,
"dirty_pages_count": integer,
@@ -108,7 +122,9 @@ All `-` values are converted to `null`
]
"major_page_fault_count_delta": integer,
"minor_page_fault_count_delta": integer,
"used": float, # [1]
"used": float,
"used_bytes": integer,
"used_unit": string,
"ipc_namespace_inode": integer,
"mount_namespace_inode": integer,
"net_namespace_inode": integer,
@@ -129,9 +145,6 @@ All `-` values are converted to `null`
}
]
[0] Values are in the units output by `top`
[1] Unit suffix stripped during float conversion
Examples:
$ top -b -n 3 | jc --top -p
@@ -338,4 +351,4 @@ Compatibility: linux
Source: [`jc/parsers/top.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/top.py)
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.3 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -48,23 +48,39 @@ Schema:
"cpu_hardware": float,
"cpu_software": float,
"cpu_steal": float,
"mem_total": float, # [0]
"mem_free": float, # [0]
"mem_used": float, # [0]
"mem_buff_cache": float, # [0]
"swap_total": float, # [0]
"swap_free": float, # [0]
"swap_used": float, # [0]
"mem_available": float, # [0]
"mem_unit": string,
"swap_unit": string,
"mem_total": float,
"mem_total_bytes": integer,
"mem_free": float,
"mem_free_bytes": integer,
"mem_used": float,
"mem_used_bytes": integer,
"mem_buff_cache": float,
"mem_buff_cache_bytes": integer,
"swap_total": float,
"swap_total_bytes": integer,
"swap_free": float,
"swap_free_bytes": integer,
"swap_used": float,
"swap_used_bytes": integer,
"mem_available": float,
"mem_available_bytes": integer,
"processes": [
{
"pid": integer,
"user": string,
"priority": integer,
"nice": integer,
"virtual_mem": float, # [1]
"resident_mem": float, # [1]
"shared_mem": float, # [1]
"virtual_mem": float,
"virtual_mem_unit": string,
"virtual_mem_bytes": integer,
"resident_mem": float,
"resident_mem_unit": string,
"resident_mem_bytes": integer,
"shared_mem": float,
"shared_mem_unit": string,
"shared_mem_bytes": integer,
"status": string,
"percent_cpu": float,
"percent_mem": float,
@@ -85,9 +101,15 @@ Schema:
"thread_count": integer,
"last_used_processor": integer,
"time": string,
"swap": float, # [1]
"code": float, # [1]
"data": float, # [1]
"swap": float,
"swap_unit": string,
"swap_bytes": integer,
"code": float,
"code_unit": string,
"code_bytes": integer
"data": float,
"data_unit": string,
"data_bytes": integer,
"major_page_fault_count": integer,
"minor_page_fault_count": integer,
"dirty_pages_count": integer,
@@ -106,7 +128,9 @@ Schema:
]
"major_page_fault_count_delta": integer,
"minor_page_fault_count_delta": integer,
"used": float, # [1]
"used": float,
"used_unit": string,
"used_bytes": integer,
"ipc_namespace_inode": integer,
"mount_namespace_inode": integer,
"net_namespace_inode": integer,
@@ -133,9 +157,6 @@ Schema:
}
}
[0] Values are in the units output by `top`
[1] Unit suffix stripped during float conversion
Examples:
$ top -b | jc --top-s
@@ -178,4 +199,4 @@ Compatibility: linux
Source: [`jc/parsers/top_s.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/top_s.py)
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.3 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -32,6 +32,8 @@ Schema:
{
"destination_ip": string,
"destination_name": string,
"max_hops": integer,
"data_bytes": integer,
"hops": [
{
"hop": integer,
@@ -54,6 +56,8 @@ Examples:
{
"destination_ip": "216.58.194.46",
"destination_name": "google.com",
"max_hops": 64,
"data_bytes": 50,
"hops": [
{
"hop": 1,
@@ -89,6 +93,8 @@ Examples:
{
"destination_ip": "216.58.194.46",
"destination_name": "google.com",
"max_hops": "64",
"data_bytes": "50",
"hops": [
{
"hop": "1",
@@ -145,4 +151,4 @@ Compatibility: linux, darwin, freebsd
Source: [`jc/parsers/traceroute.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/traceroute.py)
Version 1.7 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -0,0 +1,167 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.traceroute_s"></a>
# jc.parsers.traceroute_s
jc - JSON Convert `traceroute` command output streaming parser
> This streaming parser outputs JSON Lines (cli) or returns an Iterable of
> Dictionaries (module)
Supports `traceroute` and `traceroute6` output.
> Note: On some operating systems you will need to redirect `STDERR` to
> `STDOUT` for destination info since the header line is sent to
> `STDERR`. A warning message will be printed to `STDERR` if the
> header row is not found.
>
> e.g. `$ traceroute 8.8.8.8 2>&1 | jc --traceroute-s`
Usage (cli):
$ traceroute 1.2.3.4 | jc --traceroute-s
Usage (module):
import jc
result = jc.parse('traceroute_s', traceroute_command_output.splitlines())
for item in result:
# do something
Schema:
{
# 'header' or 'hop'
"type": string,
# 'header' type has the fields below:
"destination_ip": string,
"destination_name": string,
"max_hops": integer,
"data_bytes": integer,
# 'hop' type has the fields below:
"hop": integer,
"probes": [
{
"annotation": string,
"asn": integer,
"ip": string,
"name": string,
"rtt": float
}
]
# below object only exists if using -qq or ignore_exceptions=True
"_jc_meta": {
"success": boolean, # false if error parsing
"error": string, # exists if "success" is false
"line": string # exists if "success" is false
}
}
Examples:
$ traceroute google.com | jc --traceroute-s -p
{
"type": "header",
"destination_ip": "216.58.194.46",
"destination_name": "google.com",
"max_hops": 30,
"data_bytes": 60
}
{
"type": "hop",
"hop": 1,
"probes": [
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": 198.574
},
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": null
},
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": 198.65
}
]
}
...
$ traceroute google.com | jc --traceroute-s -p -r
{
"type": "header",
"destination_ip": "216.58.194.46",
"destination_name": "google.com",
"max_hops": "30",
"data_bytes": "60"
}
{
"type": "hop",
"hop": "1",
"probes": [
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": "198.574"
},
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": null
},
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": "198.650"
}
]
}
...
<a id="jc.parsers.traceroute_s.parse"></a>
### parse
```python
def parse(data, raw=False, quiet=False, ignore_exceptions=False)
```
Main text parsing function. Returns an iterable object.
Parameters:
data: (iterable) line-based text data to parse
(e.g. sys.stdin or str.splitlines())
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
ignore_exceptions: (boolean) ignore parsing exceptions if True
Returns:
Iterable of Dictionaries
### Parser Information
Compatibility: linux, darwin, freebsd
Source: [`jc/parsers/traceroute_s.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/traceroute_s.py)
Version 1.0 by Shintaro Kojima (goodies@codeout.net)

View File

@@ -92,4 +92,4 @@ Source: [`jc/parsers/uptime.py`](https://github.com/kellyjonbrazil/jc/blob/maste
This parser can be used with the `--slurp` command-line option.
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.10 by Kelly Brazil (kellyjonbrazil@gmail.com)

172
docs/parsers/wg_show.md Normal file
View File

@@ -0,0 +1,172 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.wg_show"></a>
# jc.parsers.wg_show
jc - JSON Convert `wg show` command output parser
Parses the output of the `wg show all dump` command, providing structured JSON output for easy integration and analysis.
Usage (cli):
$ wg show all dump | jc --wg-show
or
$ jc wg show all dump
Usage (module):
import jc
result = jc.parse('wg-show', wg_command_output)
Schema:
[
{
"device": string,
"private_key": string,
"public_key": string,
"listen_port": integer,
"fwmark": integer,
"peers": [
{
"public_key": string,
"preshared_key": string,
"endpoint": string,
"latest_handshake": integer,
"transfer_rx": integer,
"transfer_sx": integer,
"persistent_keepalive": integer,
"allowed_ips": [
string
]
}
]
}
]
Examples:
$ wg show all dump | jc --wg-show -p
[
{
"device": "wg0",
"private_key": "aEbVdvHSEp3oofHDNVCsUoaRSxk1Og8/pTLof5yF+1M=",
"public_key": "OIxbQszw1chdO5uigAxpsl4fc/h04yMYafl72gUbakM=",
"listen_port": 51820,
"fwmark": null,
"peers": [
{
"public_key": "sQFGAhSdx0aC7DmTFojzBOW8Ccjv1XV5+N9FnkZu5zc=",
"preshared_key": null,
"endpoint": "79.134.136.199:40036",
"latest_handshake": 1728809756,
"transfer_rx": 1378724,
"transfer_sx": 406524,
"persistent_keepalive": null,
"allowed_ips": ["10.10.0.2/32"]
},
{
"public_key": "B9csmpvrv4Q7gpjc6zAbNNO8hIOYfpBqxmik2aNpwwE=",
"preshared_key": null,
"endpoint": "79.134.136.199:35946",
"latest_handshake": 1728809756,
"transfer_rx": 4884248,
"transfer_sx": 3544596,
"persistent_keepalive": null,
"allowed_ips": ["10.10.0.3/32"]
},
{
"public_key": "miiSYR5UdevREhlWpmnci+vv/dEGLHbNtKu7u1CuOD4=",
"preshared_key": null,
"allowed_ips": ["10.10.0.4/32"]
},
{
"public_key": "gx9+JHLHJvOfBNjTmZ8KQAnThFFiZMQrX1kRaYcIYzw=",
"preshared_key": null,
"endpoint": "173.244.225.194:45014",
"latest_handshake": 1728809827,
"transfer_rx": 1363652,
"transfer_sx": 458252,
"persistent_keepalive": null,
"allowed_ips": ["10.10.0.5/32"]
}
]
}
]
$ wg show all dump | jc --wg-show -p -r
[
{
"device": "wg0",
"private_key": "aEbVdvHSEp3oofHDNVCsUoaRSxk1Og8/pTLof5yF+1M=",
"public_key": "OIxbQszw1chdO5uigAxpsl4fc/h04yMYafl72gUbakM=",
"listen_port": 51820,
"fwmark": null,
"peers": {
"sQFGAhSdx0aC7DmTFojzBOW8Ccjv1XV5+N9FnkZu5zc=": {
"preshared_key": null,
"endpoint": "79.134.136.199:40036",
"latest_handshake": 1728809756,
"transfer_rx": 1378724,
"transfer_sx": 406524,
"persistent_keepalive": -1,
"allowed_ips": ["10.10.0.2/32"]
},
"B9csmpvrv4Q7gpjc6zAbNNO8hIOYfpBqxmik2aNpwwE=": {
"preshared_key": null,
"endpoint": "79.134.136.199:35946",
"latest_handshake": 1728809756,
"transfer_rx": 4884248,
"transfer_sx": 3544596,
"persistent_keepalive": -1,
"allowed_ips": ["10.10.0.3/32"]
},
"miiSYR5UdevREhlWpmnci+vv/dEGLHbNtKu7u1CuOD4=": {
"preshared_key": null,
"allowed_ips": ["10.10.0.4/32"]
},
"gx9+JHLHJvOfBNjTmZ8KQAnThFFiZMQrX1kRaYcIYzw=": {
"preshared_key": null,
"endpoint": "173.244.225.194:45014",
"latest_handshake": 1728809827,
"transfer_rx": 1363652,
"transfer_sx": 458252,
"persistent_keepalive": -1,
"allowed_ips": ["10.10.0.5/32"]
}
}
}
]
<a id="jc.parsers.wg_show.parse"></a>
### parse
```python
def parse(data: str,
raw: bool = False,
quiet: bool = False) -> List[Dict[str, Any]]
```
Main text parsing function.
Parses the output of the `wg` command, specifically `wg show all dump`, into structured JSON format.
Parameters:
data: (str) Text data to parse, typically the output from `wg show all dump`
raw: (bool) If True, returns unprocessed output
quiet: (bool) Suppress warning messages if True
Returns:
List[Dict]: Parsed data in JSON-friendly format, either raw or processed.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Source: [`jc/parsers/wg_show.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/wg_show.py)
Version 1.0 by Hamza Saht (hamzasaht01@gmail.com)

View File

@@ -30,6 +30,7 @@ Schema:
"user": string,
"event": string,
"writeable_tty": string,
"process": string,
"tty": string,
"time": string,
"epoch": integer, # [0]
@@ -160,4 +161,4 @@ Compatibility: linux, darwin, cygwin, aix, freebsd
Source: [`jc/parsers/who.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/who.py)
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -435,4 +435,4 @@ Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Source: [`jc/parsers/x509_cert.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/x509_cert.py)
Version 1.3 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.4 by Kelly Brazil (kellyjonbrazil@gmail.com)

304
docs/parsers/x509_crl.md Normal file
View File

@@ -0,0 +1,304 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.x509_crl"></a>
# jc.parsers.x509_crl
jc - JSON Convert X.509 Certificate Revocation List format file parser
This parser will convert DER and PEM encoded X.509 certificate revocation
list files.
Usage (cli):
$ cat certificateRevocationList.pem | jc --x509-crl
$ cat certificateRevocationList.der | jc --x509-crl
Usage (module):
import jc
result = jc.parse('x509_crl', x509_crl_file_output)
Schema:
{
"tbs_cert_list": {
"version": string,
"signature": {
"algorithm": string,
"parameters": string/null
},
"issuer": {
"organization_name": string,
"organizational_unit_name": string,
"common_name": string
},
"this_update": integer, # [1]
"next_update": integer, # [1]
"revoked_certificates": [
{
"user_certificate": integer,
"revocation_date": integer, # [1]
"crl_entry_extensions": [
{
"extn_id": string,
"critical": boolean,
"extn_value": string,
"extn_value_iso": string
},
"revocation_date_iso": string
}
],
"crl_extensions": [
{
"extn_id": string,
"critical": boolean,
"extn_value": array/object/string/integer # [2]
}
],
"this_update_iso": string,
"next_update_iso": string
},
"signature_algorithm": {
"algorithm": string,
"parameters": string/null
},
"signature": string # [0]
}
[0] in colon-delimited hex notation
[1] time-zone-aware (UTC) epoch timestamp
[2] See below for well-known Extension schemas:
Basic Constraints:
{
"extn_id": "basic_constraints",
"critical": boolean,
"extn_value": {
"ca": boolean,
"path_len_constraint": string/null
}
}
Key Usage:
{
"extn_id": "key_usage",
"critical": boolean,
"extn_value": [
string
]
}
Key Identifier:
{
"extn_id": "key_identifier",
"critical": boolean,
"extn_value": string # [0]
}
Authority Key Identifier:
{
"extn_id": "authority_key_identifier",
"critical": boolean,
"extn_value": {
"key_identifier": string, # [0]
"authority_cert_issuer": string/null,
"authority_cert_serial_number": string/null
}
}
Subject Alternative Name:
{
"extn_id": "subject_alt_name",
"critical": boolean,
"extn_value": [
string
]
}
Certificate Policies:
{
"extn_id": "certificate_policies",
"critical": boolean,
"extn_value": [
{
"policy_identifier": string,
"policy_qualifiers": [ array or null
{
"policy_qualifier_id": string,
"qualifier": string
}
]
}
]
}
Signed Certificate Timestamp List:
{
"extn_id": "signed_certificate_timestamp_list",
"critical": boolean,
"extn_value": string # [0]
}
Examples:
$ cat sample-crl.pem | jc --x509-crl -p
{
"tbs_cert_list": {
"version": "v2",
"signature": {
"algorithm": "sha1_rsa",
"parameters": null
},
"issuer": {
"organization_name": "Sample Signer Organization",
"organizational_unit_name": "Sample Signer Unit",
"common_name": "Sample Signer Cert"
},
"this_update": 1361183520,
"next_update": 1361184120,
"revoked_certificates": [
{
"user_certificate": 1341767,
"revocation_date": 1361182932,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "affiliation_changed"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:12+00:00"
},
{
"user_certificate": 1341768,
"revocation_date": 1361182942,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "certificate_hold"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:22+00:00"
},
{
"user_certificate": 1341769,
"revocation_date": 1361182952,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "superseded"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:32+00:00"
},
{
"user_certificate": 1341770,
"revocation_date": 1361182962,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "key_compromise"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:42+00:00"
},
{
"user_certificate": 1341771,
"revocation_date": 1361182971,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "cessation_of_operation"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:51+00:00"
}
],
"crl_extensions": [
{
"extn_id": "authority_key_identifier",
"critical": false,
"extn_value": {
"key_identifier": "be:12:01:cc:aa:ea:11:80:da:2e:ad:b2...",
"authority_cert_issuer": null,
"authority_cert_serial_number": null
}
},
{
"extn_id": "crl_number",
"critical": false,
"extn_value": 3
}
],
"this_update_iso": "2013-02-18T10:32:00+00:00",
"next_update_iso": "2013-02-18T10:42:00+00:00"
},
"signature_algorithm": {
"algorithm": "sha1_rsa",
"parameters": null
},
"signature": "42:21:be:81:f1:c3:79:76:66:5b:ce:21:13:8a:68:a..."
}
<a id="jc.parsers.x509_crl.parse"></a>
### parse
```python
def parse(data: Union[str, bytes],
raw: bool = False,
quiet: bool = False) -> Dict
```
Main text parsing function
Parameters:
data: (string or bytes) text or binary data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Dictionary. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Source: [`jc/parsers/x509_crl.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/x509_crl.py)
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -111,4 +111,4 @@ Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Source: [`jc/parsers/yaml.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/yaml.py)
Version 1.7 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -23,8 +23,9 @@ jc - JSON Convert streaming utils
def add_jc_meta(func: ~F) -> ~F
```
Decorator for streaming parsers to add stream_success and stream_error
objects. This simplifies the yield lines in the streaming parsers.
Decorator for streaming parsers to add `stream_success` and
`stream_error` objects. This simplifies the `yield` lines in the
streaming parsers.
With the decorator on parse():
@@ -58,7 +59,7 @@ In all cases above:
successfully parse.
ignore_exceptions: (bool) continue processing lines and ignore
exceptions if True.
exceptions if `True`.
<a id="jc.streaming.raise_or_yield"></a>
@@ -69,8 +70,8 @@ def raise_or_yield(ignore_exceptions: bool, e: BaseException,
line: str) -> Tuple[BaseException, str]
```
Return the exception object and line string if ignore_exceptions is
True. Otherwise, re-raise the exception from the exception object with
Return the exception object and line string if `ignore_exceptions` is
`True`. Otherwise, re-raise the exception from the exception object with
an annotation.
<a id="jc.streaming.stream_error"></a>

View File

@@ -44,7 +44,7 @@ Parameters:
the parser. compatible options:
linux, darwin, cygwin, win32, aix, freebsd
quiet: (bool) suppress compatibility message if True
quiet: (bool) suppress compatibility message if `True`
Returns:
@@ -55,7 +55,10 @@ Returns:
### convert_size_to_int
```python
def convert_size_to_int(size: str, binary: bool = False) -> Optional[int]
def convert_size_to_int(size: str,
binary: bool = False,
posix_mode: bool = False,
decimal_bias: bool = False) -> Optional[int]
```
Parse a human readable data size and return the number of bytes.
@@ -66,6 +69,12 @@ Parameters:
binary: (boolean) `True` to use binary multiples of bytes
(base-2) for ambiguous unit symbols and names,
`False` to use decimal multiples of bytes (base-10).
posix_mode: (boolean) Treat one-letter units (k, m, g, etc.) as
binary.
decimal_bias: (boolean) `True` to treat slightly ambiguous two-
letter unit symbols ending in "i" (e.g. Ki, Gi) to
use decimal multiples of bytes (base-10). `False`
(default) to use binary multiples of bytes.
Returns:
integer/None Integer if successful conversion, otherwise None
@@ -85,6 +94,10 @@ gigabytes, terabytes and petabytes. Some examples:
1000
>>> convert_size_to_int('1 KiB')
1024
>>> convert_size_to_int('1 Ki')
1024
>>> convert_size_to_int('1 Ki', decimal_bias=True)
1000
>>> convert_size_to_int('1 KB', binary=True)
1024
>>> convert_size_to_int('1.5 GB')
@@ -168,7 +181,7 @@ Parameters:
Returns:
None - just prints output to STDERR
None - just prints output to `STDERR`
<a id="jc.utils.has_data"></a>
@@ -181,7 +194,7 @@ def has_data(data: Union[str, bytes]) -> bool
Checks if the string input contains data. If there are any
non-whitespace characters then return `True`, else return `False`.
For bytes, returns True if there is any data.
For bytes, returns `True` if there is any data.
Parameters:
@@ -189,9 +202,9 @@ Parameters:
Returns:
Boolean True if input string (data) contains non-whitespace
characters, otherwise False. For bytes data, returns
True if there is any data, otherwise False.
Boolean `True` if input string (data) contains non-whitespace
characters, otherwise `False`. For bytes data, returns
`True` if there is any data, otherwise `False`.
<a id="jc.utils.input_type_check"></a>

View File

@@ -52,7 +52,7 @@ class info():
author: str = 'Kelly Brazil'
author_email: str = 'kellyjonbrazil@gmail.com'
website: str = 'https://github.com/kellyjonbrazil/jc'
copyright: str = '© 2019-2024 Kelly Brazil'
copyright: str = '© 2019-2025 Kelly Brazil'
license: str = 'MIT License'
@@ -401,11 +401,16 @@ class JcCli():
self.json_indent = 2
self.json_separators = None
# Convert any non-serializable object to a string
def string_serializer(data):
return str(data)
j_string = json.dumps(
self.data_out,
indent=self.json_indent,
separators=self.json_separators,
ensure_ascii=self.ascii_only
ensure_ascii=self.ascii_only,
default=string_serializer
)
if not self.mono and PYGMENTS_INSTALLED:

View File

@@ -101,8 +101,8 @@ Examples:
$ jc --pretty /proc/meminfo
Line Slicing:
$ cat output.txt | jc 4:15 --parser # Parse from line 4 to 14
with parser (zero-based)
$ cat output.txt | jc 4:15 --<PARSER> # Parse from line 4 to 14
# with <PARSER> (zero-based)
Parser Documentation:
$ jc --help --dig

View File

@@ -3,6 +3,7 @@
import sys
from typing import Any, Dict, List, Tuple, Iterator, Optional, Union
CustomColorType = Dict[Any, str]
JSONDictType = Dict[str, Any]
StreamingOutputType = Iterator[Union[JSONDictType, Tuple[BaseException, str]]]
@@ -42,11 +43,3 @@ if sys.version_info >= (3, 8):
else:
ParserInfoType = Dict
TimeStampFormatType = Dict
try:
from pygments.token import (Name, Number, String, Keyword)
CustomColorType = Dict[Union[Name.Tag, Number, String, Keyword], str]
except Exception:
CustomColorType = Dict # type: ignore

View File

@@ -10,12 +10,13 @@ from jc import appdirs
from jc import utils
__version__ = '1.25.3'
__version__ = '1.25.6'
parsers: List[str] = [
'acpi',
'airport',
'airport-s',
'amixer',
'apt-cache-show',
'apt-get-sqq',
'arp',
@@ -75,6 +76,7 @@ parsers: List[str] = [
'iostat',
'iostat-s',
'ip-address',
'ipconfig',
'iptables',
'ip-route',
'iw-scan',
@@ -101,12 +103,15 @@ parsers: List[str] = [
'mpstat-s',
'needrestart',
'netstat',
'net-localgroup',
'net-user',
'nmcli',
'nsd-control',
'ntpq',
'openvpn',
'os-prober',
'os-release',
'pacman',
'passwd',
'path',
'path-list',
@@ -177,6 +182,7 @@ parsers: List[str] = [
'ps',
'resolve-conf',
'route',
'route-print',
'rpm-qi',
'rsync',
'rsync-s',
@@ -208,6 +214,7 @@ parsers: List[str] = [
'top-s',
'tracepath',
'traceroute',
'traceroute-s',
'tune2fs',
'udevadm',
'ufw',
@@ -224,8 +231,10 @@ parsers: List[str] = [
'vmstat-s',
'w',
'wc',
'wg-show',
'who',
'x509-cert',
'x509-crl',
'x509-csr',
'xml',
'xrandr',
@@ -342,7 +351,8 @@ def _get_parser(parser_mod_name: str) -> ModuleType:
def _parser_is_slurpable(parser: ModuleType) -> bool:
"""
Returns True if this parser can use the `--slurp` command option, else False
Returns `True` if this parser can use the `--slurp` command option, else
`False`
parser is a parser module object.
"""
@@ -354,7 +364,7 @@ def _parser_is_slurpable(parser: ModuleType) -> bool:
def _parser_is_streaming(parser: ModuleType) -> bool:
"""
Returns True if this is a streaming parser, else False
Returns `True` if this is a streaming parser, else `False`
parser is a parser module object.
"""
@@ -365,7 +375,7 @@ def _parser_is_streaming(parser: ModuleType) -> bool:
def _parser_is_hidden(parser: ModuleType) -> bool:
"""
Returns True if this is a hidden parser, else False
Returns `True` if this is a hidden parser, else `False`
parser is a parser module object.
"""
@@ -376,7 +386,7 @@ def _parser_is_hidden(parser: ModuleType) -> bool:
def _parser_is_deprecated(parser: ModuleType) -> bool:
"""
Returns True if this is a deprecated parser, else False
Returns `True` if this is a deprecated parser, else `False`
parser is a parser module object.
"""
@@ -463,17 +473,17 @@ def parse(
variants of the module name.
A Module object can also be passed
directly or via get_parser()
directly or via `get_parser()`
data: (string or data to parse (string or bytes for
bytes or standard parsers, iterable of
iterable) strings for streaming parsers)
raw: (boolean) output preprocessed JSON if True
raw: (boolean) output preprocessed JSON if `True`
quiet: (boolean) suppress warning messages if True
quiet: (boolean) suppress warning messages if `True`
ignore_exceptions: (boolean) ignore parsing exceptions if True
ignore_exceptions: (boolean) ignore parsing exceptions if `True`
(streaming parsers only)
Returns:
@@ -623,7 +633,7 @@ def parser_info(
variants of the module name as well
as a parser module object.
documentation: (boolean) include parser docstring if True
documentation: (boolean) include parser docstring if `True`
"""
parser_mod = get_parser(parser_mod_name)
parser_mod_name = parser_mod.__name__.split('.')[-1]
@@ -660,7 +670,7 @@ def all_parser_info(
Parameters:
documentation: (boolean) include parser docstrings if True
documentation: (boolean) include parser docstrings if `True`
show_hidden: (boolean) also show parsers marked as hidden
in their info metadata.
show_deprecated: (boolean) also show parsers marked as

273
jc/parsers/amixer.py Normal file
View File

@@ -0,0 +1,273 @@
r"""jc - JSON Convert `amixer sget` command output parser
Usage (cli):
$ amixer sget <control_name> | jc --amixer
$ amixer sget Master | jc --amixer
$ amixer sget Capture | jc --amixer
$ amixer sget Speakers | jc --amixer
Usage (module):
import jc
result = jc.parse('amixer', <amixer_sget_command_output>)
Schema:
{
"control_name": string,
"capabilities": [
string
],
"playback_channels": [
string
],
"limits": {
"playback_min": integer,
"playback_max": integer
},
"mono": {
"playback_value": integer,
"percentage": integer,
"db": float,
"status": boolean
}
}
Examples:
$ amixer sget Master | jc --amixer -p
{
"control_name": "Capture",
"capabilities": [
"cvolume",
"cswitch"
],
"playback_channels": [],
"limits": {
"playback_min": 0,
"playback_max": 63
},
"front_left": {
"playback_value": 63,
"percentage": 100,
"db": 30.0,
"status": true
},
"front_right": {
"playback_value": 63,
"percentage": 100,
"db": 30.0,
"status": true
}
}
$ amixer sget Master | jc --amixer -p -r
{
"control_name": "Master",
"capabilities": [
"pvolume",
"pvolume-joined",
"pswitch",
"pswitch-joined"
],
"playback_channels": [
"Mono"
],
"limits": {
"playback_min": "0",
"playback_max": "87"
},
"mono": {
"playback_value": "87",
"percentage": "100%",
"db": "0.00db",
"status": "on"
}
}
"""
from typing import Dict
import jc.utils
from jc.utils import convert_to_int
from jc.exceptions import ParseError
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`amixer` command parser'
author = 'Eden Refael'
author_email = 'edenraf@hotmail.com'
compatible = ['linux']
magic_commands = ['amixer']
tags = ['command']
__version__ = info.version
def _process(proc_data: Dict) -> Dict:
"""
Processes raw structured data to match the schema requirements.
Parameters:
proc_data: (dict) raw structured data from the parser
Returns:
(dict) processed structured data adhering to the schema
"""
if not proc_data:
return {}
# Initialize the processed dictionary
processed = {
"control_name": proc_data.get("control_name", ""),
"capabilities": proc_data.get("capabilities", []),
"playback_channels": proc_data.get("playback_channels", []),
"limits": {
"playback_min": convert_to_int(proc_data.get("limits", {}).get("playback_min", 0)),
"playback_max": convert_to_int(proc_data.get("limits", {}).get("playback_max", 0)),
},
}
# Process Mono or channel-specific data
channels = ["mono", "front_left", "front_right"]
for channel in channels:
if channel in proc_data:
channel_data = proc_data[channel]
processed[channel] = {
"playback_value": convert_to_int(channel_data.get("playback_value", 0)),
"percentage": convert_to_int(channel_data.get("percentage", "0%").strip("%")),
"db": float(channel_data.get("db", "0.0db").strip("db")),
"status": channel_data.get("status", "off") == "on",
}
return processed
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> Dict:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Dictionary. Raw or processed structured data.
"""
"""
The Algorithm for parsing the `amixer sget` command, Input Explained/Rules/Pseudo Algorithm:
1. There will always be the first line which tells the user about the control name.
2. There will always be the Capabilities which include many of capabilities - It will be listed and separated by `" "`.
3. After that we'll need to distinct between the Channel - Could be many of channels - It will be listed and separated
by `" "`.
3a. Capture channels - List of channels
3b. Playback channels - List of channels
4. Limits - We'll always have the minimum limit and the maximum limit.
Input Example:
1."":~$ amixer sget Capture
Simple mixer control 'Capture',0
Capabilities: cvolume cswitch
Capture channels: Front Left - Front Right
Limits: Capture 0 - 63
Front Left: Capture 63 [100%] [30.00db] [on]
Front Right: Capture 63 [100%] [30.00db] [on]
2."":~$ amixer sget Master
Simple mixer control 'Master',0
Capabilities: pvolume pvolume-joined pswitch pswitch-joined
Playback channels: Mono
Limits: Playback 0 - 87
Mono: Playback 87 [100%] [0.00db] [on]
3."":~$ amixer sget Speaker
Simple mixer control 'Speaker',0
Capabilities: pvolume pswitch
Playback channels: Front Left - Front Right
Limits: Playback 0 - 87
Mono:
Front Left: Playback 87 [100%] [0.00db] [on]
Front Right: Playback 87 [100%] [0.00db] [on]
4."":~$ amixer sget Headphone
Simple mixer control 'Headphone',0
Capabilities: pvolume pswitch
Playback channels: Front Left - Front Right
Limits: Playback 0 - 87
Mono:
Front Left: Playback 0 [0%] [-65.25db] [off]
Front Right: Playback 0 [0%] [-65.25db] [off]
"""
# checks os compatibility and print a stderr massage if not compatible. quiet True could remove this check.
jc.utils.compatibility(__name__, info.compatible, quiet)
# check if string
jc.utils.input_type_check(data)
# starts the parsing from here
mapping: Dict = {}
if jc.utils.has_data(data):
# split lines and than work on each line
lines = data.splitlines()
first_line = lines[0].strip()
# Extract the control name from the first line
if first_line.startswith("Simple mixer control"):
control_name = first_line.split("'")[1]
else:
raise ParseError("Invalid amixer output format: missing control name.")
# map the control name
mapping["control_name"] = control_name
# Process subsequent lines for capabilities, channels, limits, and channel-specific mapping.
# gets the lines from the next line - because we already took care the first line.
for line in lines[1:]:
# strip the line (maybe there are white spaces in the begin&end)
line = line.strip()
if line.startswith("Capabilities:"):
mapping["capabilities"] = line.split(":")[1].strip().split()
elif line.startswith("Playback channels:"):
mapping["playback_channels"] = line.split(":")[1].strip().split(" - ")
elif line.startswith("Limits:"):
limits = line.split(":")[1].strip().split(" - ")
mapping["limits"] = {
"playback_min": limits[0].split()[1],
"playback_max": limits[1]
}
elif line.startswith("Mono:") or line.startswith("Front Left:") or line.startswith("Front Right:"):
# Identify the channel name and parse its information
channel_name = line.split(":")[0].strip().lower().replace(" ", "_")
channel_info = line.split(":")[1].strip()
# Example: "Playback 255 [100%] [0.00db] [on]"
channel_data = channel_info.split(" ")
if channel_data[0] == "":
continue
if "dB" in channel_data[3]:
db_value = channel_data[3].strip("[]")
status = channel_data[4].strip("[]")
else:
db_value = "0.0db"
status = channel_data[3].strip("[]")
playback_value = channel_data[1]
percentage = channel_data[2].strip("[]") # Extract percentage e.g., "100%"
# Store channel mapping in the dictionary
mapping[channel_name] = {
"playback_value": playback_value,
"percentage": percentage,
"db": db_value.lower(),
"status": status
}
return mapping if raw else _process(mapping)

View File

@@ -255,7 +255,7 @@ class EmailAddress(IA5String):
# fix to allow incorrectly encoded email addresses to succeed with warning
try:
self._unicode = mailbox.decode('cp1252') + '@' + hostname.decode('idna')
except UnicodeDecodeError:
except (UnicodeDecodeError, UnicodeError):
ascii_mailbox = mailbox.decode('ascii', errors='backslashreplace')
ascii_hostname = hostname.decode('ascii', errors='backslashreplace')
from jc.utils import warning_message

View File

@@ -28,6 +28,8 @@ a controller and a device but there might be fields corresponding to one entity.
Controller:
[
{
"manufacturer": string,
"version": string,
"name": string,
"is_default": boolean,
"is_public": boolean,
@@ -62,6 +64,7 @@ a controller and a device but there might be fields corresponding to one entity.
"blocked": string,
"connected": string,
"legacy_pairing": string,
"cable_pairing": string,
"rssi": int,
"txpower": int,
"uuids": array,
@@ -110,7 +113,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.2'
version = '1.5'
description = '`bluetoothctl` command parser'
author = 'Jake Ob'
author_email = 'iakopap at gmail.com'
@@ -127,6 +130,8 @@ try:
Controller = TypedDict(
"Controller",
{
"manufacturer": str,
"version": str,
"name": str,
"is_default": bool,
"is_public": bool,
@@ -135,6 +140,7 @@ try:
"alias": str,
"class": str,
"powered": str,
"power_state": str,
"discoverable": str,
"discoverable_timeout": str,
"pairable": str,
@@ -160,6 +166,7 @@ try:
"blocked": str,
"connected": str,
"legacy_pairing": str,
"cable_pairing": str,
"rssi": int,
"txpower": int,
"uuids": List[str],
@@ -175,10 +182,13 @@ except ImportError:
_controller_head_pattern = r"Controller (?P<address>([0-9A-F]{2}:){5}[0-9A-F]{2}) (?P<name>.+)"
_controller_line_pattern = (
r"(\s*Name:\s*(?P<name>.+)"
r"(\s*Manufacturer:\s*(?P<manufacturer>.+)"
+ r"|\s*Version:\s*(?P<version>.+)"
+ r"|\s*Name:\s*(?P<name>.+)"
+ r"|\s*Alias:\s*(?P<alias>.+)"
+ r"|\s*Class:\s*(?P<class>.+)"
+ r"|\s*Powered:\s*(?P<powered>.+)"
+ r"|\s*PowerState:\s*(?P<power_state>.+)"
+ r"|\s*Discoverable:\s*(?P<discoverable>.+)"
+ r"|\s*DiscoverableTimeout:\s*(?P<discoverable_timeout>.+)"
+ r"|\s*Pairable:\s*(?P<pairable>.+)"
@@ -203,6 +213,8 @@ def _parse_controller(next_lines: List[str]) -> Optional[Controller]:
return None
controller: Controller = {
"manufacturer": '',
"version": '',
"name": '',
"is_default": False,
"is_public": False,
@@ -211,6 +223,7 @@ def _parse_controller(next_lines: List[str]) -> Optional[Controller]:
"alias": '',
"class": '',
"powered": '',
"power_state": '',
"discoverable": '',
"discoverable_timeout": '',
"pairable": '',
@@ -241,7 +254,11 @@ def _parse_controller(next_lines: List[str]) -> Optional[Controller]:
matches = result.groupdict()
if matches["name"]:
if matches["manufacturer"]:
controller["manufacturer"] = matches["manufacturer"]
elif matches["version"]:
controller["version"] = matches["version"]
elif matches["name"]:
controller["name"] = matches["name"]
elif matches["alias"]:
controller["alias"] = matches["alias"]
@@ -249,6 +266,8 @@ def _parse_controller(next_lines: List[str]) -> Optional[Controller]:
controller["class"] = matches["class"]
elif matches["powered"]:
controller["powered"] = matches["powered"]
elif matches["power_state"]:
controller["power_state"] = matches["power_state"]
elif matches["discoverable"]:
controller["discoverable"] = matches["discoverable"]
elif matches["discoverable_timeout"]:
@@ -285,6 +304,7 @@ _device_line_pattern = (
+ r"|\s*TxPower:\s*(?P<txpower>.+)"
+ r"|\s*Battery\sPercentage:\s*0[xX][0-9a-fA-F]*\s*\((?P<battery_percentage>[0-9]+)\)"
+ r"|\s*UUID:\s*(?P<uuid>.+))"
+ r"|\s*CablePairing:\s*(?P<cable_pairing>.+)"
)
@@ -318,6 +338,7 @@ def _parse_device(next_lines: List[str], quiet: bool) -> Optional[Device]:
"blocked": '',
"connected": '',
"legacy_pairing": '',
"cable_pairing": '',
"rssi": 0,
"txpower": 0,
"uuids": [],
@@ -366,6 +387,8 @@ def _parse_device(next_lines: List[str], quiet: bool) -> Optional[Device]:
device["connected"] = matches["connected"]
elif matches["legacy_pairing"]:
device["legacy_pairing"] = matches["legacy_pairing"]
elif matches["cable_pairing"]:
device["cable_pairing"] = matches["cable_pairing"]
elif matches["rssi"]:
rssi = matches["rssi"]
try:

View File

@@ -101,7 +101,7 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '2.0'
version = '2.1'
description = '`df` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -127,6 +127,7 @@ def _process(proc_data):
"""
int_list = {'use_percent', 'capacity_percent', 'ifree', 'iused', 'iused_percent'}
size_list = {'size', 'used', 'available'}
posix_mode = False
for entry in proc_data:
if 'avail' in entry:
@@ -134,6 +135,7 @@ def _process(proc_data):
if 'use%' in entry:
entry['use_percent'] = entry.pop('use%')
posix_mode = True
if 'capacity' in entry:
entry['capacity_percent'] = entry.pop('capacity')
@@ -159,7 +161,7 @@ def _process(proc_data):
# parse the size, used, and available fields to bytes
for key in entry:
if key in size_list:
entry[key] = jc.utils.convert_size_to_int(entry[key])
entry[key] = jc.utils.convert_size_to_int(entry[key], posix_mode=posix_mode)
# convert integers
for key in entry:

View File

@@ -172,7 +172,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
version = '1.1'
description = '`ethtool` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -248,10 +248,11 @@ def _parse_default(data: str) -> JSONDictType:
supported_link_modes: List[str] = []
supported_fec_modes: List[str] = []
advertised_link_modes: List[str] = []
link_partner_advertised_link_modes: List[str] = []
advertised_fec_modes: List[str] = []
current_message_level: List[str] = []
mode: str = '' # supported_link_modes, supported_fec_modes, advertised_link_modes,
# advertised_fec_modes, current_message_level
# link_partner_advertised_link_modes, advertised_fec_modes, current_message_level
for line in filter(None, data.splitlines()):
@@ -294,6 +295,14 @@ def _parse_default(data: str) -> JSONDictType:
mode = 'advertised_link_modes'
continue
if 'Link partner advertised link modes:' in line and 'Not reported' not in line:
_, val = line.split(':', maxsplit=1)
val = val.strip()
val_list = val.split()
link_partner_advertised_link_modes.extend(val_list)
mode = 'link_partner_advertised_link_modes'
continue
if 'Advertised FEC modes:' in line and 'Not reported' not in line:
_, val = line.split(':', maxsplit=1)
val = val.strip()
@@ -326,6 +335,12 @@ def _parse_default(data: str) -> JSONDictType:
advertised_link_modes.extend(val_list)
continue
if mode == 'link_partner_advertised_link_modes':
val = line.strip()
val_list = val.split()
link_partner_advertised_link_modes.extend(val_list)
continue
if mode == 'advertised_fec_modes':
val = line.strip()
val_list = val.split()
@@ -346,6 +361,7 @@ def _parse_default(data: str) -> JSONDictType:
(supported_link_modes, 'supported_link_modes'),
(supported_fec_modes, 'supported_fec_modes'),
(advertised_link_modes, 'advertised_link_modes'),
(link_partner_advertised_link_modes, 'link_partner_advertised_link_modes'),
(advertised_fec_modes, 'advertised_fec_modes'),
(current_message_level, 'current_message_level')
]

View File

@@ -212,14 +212,14 @@ Examples:
"""
import re
from ipaddress import IPv4Network
from typing import List, Dict, Optional
from typing import List, Dict
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '2.3'
version = '2.4'
description = '`ifconfig` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -548,6 +548,11 @@ def parse(
broadcast\s+(?P<broadcast>(?:[0-9]{1,3}\.){3}[0-9]{1,3}))?
''', re.IGNORECASE | re.VERBOSE
)
re_freebsd_ipv4_utun = re.compile(r'''
inet\s(?P<address>(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s-->\s(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s
netmask\s(?P<mask>\S*)
''', re.IGNORECASE | re.VERBOSE
)
re_freebsd_ipv6 = re.compile(r'''
\s?inet6\s(?P<address>.*?)
(?:\%(?P<scope_id>\w+\d+))?\s
@@ -624,14 +629,14 @@ def parse(
re_openbsd_rx_stats, re_openbsd_tx, re_openbsd_tx_stats
]
re_freebsd = [
re_freebsd_interface, re_freebsd_ipv4, re_freebsd_ipv4_v2, re_freebsd_ipv6,
re_freebsd_interface, re_freebsd_ipv4, re_freebsd_ipv4_v2, re_freebsd_ipv4_utun, re_freebsd_ipv6,
re_freebsd_details, re_freebsd_status, re_freebsd_nd6_options, re_freebsd_plugged,
re_freebsd_vendor_pn_sn_date, re_freebsd_temp_volts, re_freebsd_hwaddr, re_freebsd_media,
re_freebsd_tx_rx_power, re_freebsd_options
]
interface_patterns = [re_linux_interface, re_openbsd_interface, re_freebsd_interface]
ipv4_patterns = [re_linux_ipv4, re_openbsd_ipv4, re_freebsd_ipv4, re_freebsd_ipv4_v2]
ipv4_patterns = [re_linux_ipv4, re_openbsd_ipv4, re_freebsd_ipv4, re_freebsd_ipv4_v2, re_freebsd_ipv4_utun]
ipv6_patterns = [re_linux_ipv6, re_openbsd_ipv6, re_freebsd_ipv6]
if jc.utils.has_data(data):

View File

@@ -533,7 +533,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.4'
version = '1.5'
description = 'IPv4 and IPv6 Address string parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -661,8 +661,18 @@ def parse(
if interface.version == 4:
ip_split = ip_exploded.split('.')
else:
ip_split = ip_exploded.split(':')
# regular IPv6
if not '.' in ip_exploded:
ip_split = ip_exploded.split(':')
# IPv4 mapped IPv6
else:
ip_split_v6 = ip_exploded.split(':')[:-1]
ip_split_v4_mapped_str = ip_exploded.split(':')[-1]
ip_split_v4_mapped = ip_split_v4_mapped_str.split('.')
ip_split = ip_split_v6 + ip_split_v4_mapped
# fix for ipv6-only attributes
scope_id = None

809
jc/parsers/ipconfig.py Normal file
View File

@@ -0,0 +1,809 @@
r"""jc - JSON Convert `ipconfig` Windows command output parser
Usage (cli):
$ ipconfig /all | jc --ipconfig
$ ipconfig | jc --ipconfig
$ jc ipconfig /all
Usage (module):
import jc
result = jc.parse('ipconfig', ipconfig_command_output)
Schema:
{
"host_name": string,
"primary_dns_suffix": string,
"node_type": string,
"ip_routing_enabled": boolean,
"wins_proxy_enabled": boolean,
"dns_suffix_search_list": [
string
],
"adapters": [
{
"name_long": string,
"name": string,
"type": string,
"connection_specific_dns_suffix": string,
"connection_specific_dns_suffix_search_list": [
string
]
"description": string,
"physical_address": string,
"dhcp_enabled": boolean,
"autoconfiguration_enabled": boolean,
"ipv6_addresses": [
{
"address": string,
"status": string,
},
],
"temporary_ipv6_addresses": [
{
"address": string,
"status": string,
},
],
"link_local_ipv6_addresses": [
{
"address": string,
"status": string,
"prefix_length": integer,
}
],
"ipv4_addresses": [
{
"address": string, # [2]
"subnet_mask": string,
"status": string,
"autoconfigured": boolean # [1]
}
],
"default_gateways": [
string
],
"dhcp_server": null,
"dhcpv6_iaid": string,
"dhcpv6_client_duid": string,
"dns_servers": [
string
],
"primary_wins_server": string,
"lease_expires": string,
"lease_expires_epoch": integer, # [0]
"lease_expires_iso": string,
"lease_obtained": string,
"lease_obtained_epoch": integer, # [0]
"lease_obtained_iso": string,
"netbios_over_tcpip": boolean,
"media_state": string,
"extras": [
<string>: string
]
}
],
"extras": []
}
Notes:
[0] - The epoch calculated timestamp field is naive. (i.e. based on
the local time of the system the parser is run on)
[1] - 'autoconfigured' under 'ipv4_address' is only providing
indication if the ipv4 address was labeled as "Autoconfiguration
IPv4 Address" vs "IPv4 Address". It does not infer any
information from other fields
[2] - Windows XP uses 'IP Address' instead of 'IPv4 Address'. Both
values are parsed to the 'ipv4_address' object for consistency
Examples:
$ ipconfig /all | jc --ipconfig -p
{
"host_name": "DESKTOP-WIN11-HOME",
"primary_dns_suffix": null,
"node_type": "Hybrid",
"ip_routing_enabled": false,
"wins_proxy_enabled": false,
"dns_suffix_search_list": [
"localdomain"
],
"adapters": [
{
"name_long": "Ethernet adapter Ethernet",
"name": "Ethernet",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Intel(R) I211 Gigabit Network Connection",
"physical_address": "24-4B-FE-AB-43-C3",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Ethernet adapter Ethernet 2",
"name": "Ethernet 2",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Realtek PCIe 2.5GbE Family Controller",
"physical_address": "24-4B-FE-57-3D-F2",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Unknown adapter OpenVPN Data Channel Offload for NordVPN",
"name": "OpenVPN Data Channel Offload for NordVPN",
"type": "Unknown",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "OpenVPN Data Channel Offload",
"physical_address": null,
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Unknown adapter Local Area Connection",
"name": "Local Area Connection",
"type": "Unknown",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "TAP-NordVPN Windows Adapter V9",
"physical_address": "00-FF-4C-F4-5E-49",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Wireless LAN adapter Local Area Connection* 1",
"name": "Local Area Connection* 1",
"type": "Wireless LAN",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Microsoft Wi-Fi Direct Virtual Adapter",
"physical_address": "A8-7E-EA-5A-7F-DE",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Wireless LAN adapter Local Area Connection* 2",
"name": "Local Area Connection* 2",
"type": "Wireless LAN",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Microsoft Wi-Fi Direct Virtual Adapter #2",
"physical_address": "AA-7E-EA-F3-64-C3",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
},
{
"name_long": "Ethernet adapter VMware Network Adapter VMnet1",
"name": "VMware Network Adapter VMnet1",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "VMware Virtual Ethernet Adapter for VMnet1",
"physical_address": "00-50-56-CC-27-73",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [
{
"address": "fe80::f47d:9c7f:69dc:591e",
"prefix_length": 8,
"status": "Preferred"
}
],
"ipv4_addresses": [
{
"address": "192.168.181.1",
"subnet_mask": "255.255.255.0",
"status": "Preferred",
"autoconfigured": false
}
],
"default_gateways": [],
"dhcp_server": "192.168.181.254",
"dhcpv6_iaid": "771772502",
"dhcpv6_client_duid": "00-01-00-01-2C-CF-19-EB-24-4B-FE-5B-9B-E6",
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": "2024-09-19T18:01:29",
"lease_obtained": "2024-09-19T08:31:29",
"netbios_over_tcpip": true,
"media_state": null,
"extras": []
},
{
"name_long": "Ethernet adapter VMware Network Adapter VMnet8",
"name": "VMware Network Adapter VMnet8",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "VMware Virtual Ethernet Adapter for VMnet8",
"physical_address": "00-50-56-C9-A3-78",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [
{
"address": "fe80::4551:bf0d:59dd:a4f0",
"prefix_length": 10,
"status": "Preferred"
}
],
"ipv4_addresses": [
{
"address": "192.168.213.1",
"subnet_mask": "255.255.255.0",
"status": "Preferred",
"autoconfigured": false
}
],
"default_gateways": [],
"dhcp_server": "192.168.213.254",
"dhcpv6_iaid": "788549718",
"dhcpv6_client_duid": "00-01-00-01-2C-CF-19-EB-24-4B-FE-5B-9B-E6",
"dns_servers": [],
"primary_wins_server": "192.168.213.2",
"lease_expires": "2024-09-19T18:01:29",
"lease_obtained": "2024-09-19T08:31:29",
"netbios_over_tcpip": true,
"media_state": null,
"extras": []
},
{
"name_long": "Wireless LAN adapter Wi-Fi",
"name": "Wi-Fi",
"type": "Wireless LAN",
"connection_specific_dns_suffix": "localdomain",
"connection_specific_dns_suffix_search_list": [],
"description": "Intel(R) Wi-Fi 6 AX200 160MHz",
"physical_address": "A8-7E-EA-55-26-B0",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [
{
"address": "fd63:cc9c:65eb:3f95:57c2:aa:10d8:db08",
"status": "Preferred"
}
],
"temporary_ipv6_addresses": [
{
"address": "fd63:cc9c:65eb:3f95:8928:348e:d692:b7ef",
"status": "Preferred"
}
],
"link_local_ipv6_addresses": [
{
"address": "fe80::4fae:1380:5a1b:8b6b",
"prefix_length": 11,
"status": "Preferred"
}
],
"ipv4_addresses": [
{
"address": "192.168.1.169",
"subnet_mask": "255.255.255.0",
"status": "Preferred",
"autoconfigured": false
}
],
"default_gateways": [
"192.168.1.1"
],
"dhcp_server": "192.168.1.1",
"dhcpv6_iaid": "162037482",
"dhcpv6_client_duid": "00-01-00-01-2C-CF-19-EB-24-4B-FE-5B-9B-E6",
"dns_servers": [
"192.168.1.1"
],
"primary_wins_server": null,
"lease_expires": "2024-09-20T08:31:30",
"lease_obtained": "2024-09-19T08:31:30",
"netbios_over_tcpip": true,
"media_state": null,
"extras": []
},
{
"name_long": "Ethernet adapter Bluetooth Network Connection",
"name": "Bluetooth Network Connection",
"type": "Ethernet",
"connection_specific_dns_suffix": null,
"connection_specific_dns_suffix_search_list": [],
"description": "Bluetooth Device (Personal Area Network)",
"physical_address": "A8-7E-EA-43-23-14",
"dhcp_enabled": true,
"autoconfiguration_enabled": true,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": null,
"dhcpv6_iaid": null,
"dhcpv6_client_duid": null,
"dns_servers": [],
"primary_wins_server": null,
"lease_expires": null,
"lease_obtained": null,
"netbios_over_tcpip": null,
"media_state": "Media disconnected",
"extras": []
}
],
"extras": []
}
"""
from datetime import datetime
import re
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`ipconfig` Windows command parser'
author = 'joehacksalot'
author_email = 'joehacksalot@gmail.com'
compatible = ['windows']
magic_commands = ['ipconfig']
tags = ['command']
__version__ = info.version
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Parsed dictionary. The raw and processed data structures are the same.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output = {}
if jc.utils.has_data(data):
# Initialize the parsed output dictionary with all fields set to None or empty lists
raw_output = _parse(data)
return raw_output if raw else _process(raw_output)
def _process_ipv6_address(ip_address):
address_split = ip_address["address"].split('%')
try:
if len(address_split) > 1:
address = address_split[0]
prefix_length = int(address_split[1])
else:
address = ip_address["address"]
prefix_length = None
except:
address = ip_address["address"]
prefix_length = None
return {
"address": address,
"prefix_length": prefix_length,
"status": ip_address["status"]
}
def _process_ipv4_address(ip_address):
autoconfigured = True if ip_address.get("autoconfigured","") is not None and 'autoconfigured' in ip_address.get("autoconfigured","") else False
subnet_mask = ip_address["subnet_mask"]
return {
"address": ip_address["address"],
"subnet_mask": subnet_mask,
"status": ip_address["status"],
"autoconfigured": autoconfigured
}
def _process(proc_data):
"""
Final processing to conform to the schema.
Parameters:
proc_data: (Dictionary) raw structured data to process
Returns:
Processed Dictionary. Structured data to conform to the schema.
"""
processed = proc_data
if "ip_routing_enabled" in processed and processed["ip_routing_enabled"] is not None:
processed["ip_routing_enabled"] = (processed["ip_routing_enabled"].lower() == "yes")
if "wins_proxy_enabled" in processed and processed["wins_proxy_enabled"] is not None:
processed["wins_proxy_enabled"] = (processed["wins_proxy_enabled"].lower() == "yes")
for adapter in processed["adapters"]:
if "dhcp_enabled" in adapter and adapter["dhcp_enabled"] is not None:
adapter["dhcp_enabled"] = (adapter["dhcp_enabled"].lower() == "yes")
if "autoconfiguration_enabled" in adapter and adapter["autoconfiguration_enabled"] is not None:
adapter["autoconfiguration_enabled"] = (adapter["autoconfiguration_enabled"].lower() == "yes")
if "netbios_over_tcpip" in adapter and adapter["netbios_over_tcpip"] is not None:
adapter["netbios_over_tcpip"] = (adapter["netbios_over_tcpip"].lower() == "enabled")
if "lease_expires" in adapter and adapter["lease_expires"]:
ts = jc.utils.timestamp(adapter['lease_expires'], format_hint=(1720,))
adapter["lease_expires_epoch"] = ts.naive
adapter["lease_expires_iso"] = ts.iso
if "lease_obtained" in adapter and adapter["lease_obtained"]:
ts = jc.utils.timestamp(adapter['lease_obtained'], format_hint=(1720,))
adapter["lease_obtained_epoch"] = ts.naive
adapter["lease_obtained_iso"] = ts.iso
adapter["link_local_ipv6_addresses"] = [_process_ipv6_address(address) for address in adapter.get("link_local_ipv6_addresses", [])]
adapter["ipv4_addresses"] = [_process_ipv4_address(address) for address in adapter.get("ipv4_addresses", [])]
return processed
class _PushbackIterator:
def __init__(self, iterator):
self.iterator = iterator
self.pushback_stack = []
def __iter__(self):
return self
def __next__(self):
if self.pushback_stack:
return self.pushback_stack.pop()
else:
return next(self.iterator)
def pushback(self, value):
self.pushback_stack.append(value)
def _parse(data):
# Initialize the parsed output dictionary with all fields set to None or empty lists
parse_output = {
"host_name": None,
"primary_dns_suffix": None,
"node_type": None,
"ip_routing_enabled": None,
"wins_proxy_enabled": None,
"dns_suffix_search_list": [],
"adapters": [],
"extras": [] # To store unrecognized fields
}
lines = data.splitlines()
lines = [line.rstrip() for line in lines if line.strip() != ""]
line_iter = _PushbackIterator(iter(lines))
adapter = None
in_adapter_section = False
for line in line_iter:
line = line.rstrip()
# Skip empty lines
if not line.strip():
continue
# Header Section
if not in_adapter_section:
if "Windows IP Configuration" in line:
continue
elif _is_adapter_start_line(line):
# Start of Adapter Section
in_adapter_section = True
adapter_name = line.strip(":").strip()
adapter = _initialize_adapter(adapter_name)
parse_output["adapters"].append(adapter)
elif line.startswith(" "):
key, value = _parse_line(line)
if key:
_parse_header_line(parse_output, key, value, line_iter)
else:
continue
else:
# Adapter Sections
if _is_adapter_start_line(line):
# Start of new adapter
adapter_name = line.strip(":").strip()
adapter = _initialize_adapter(adapter_name)
parse_output["adapters"].append(adapter)
elif line.startswith(" "):
key, value = _parse_line(line)
if key:
_parse_adapter_line(adapter, key, value, line_iter)
else:
continue
return parse_output
def _is_adapter_start_line(line):
# Detect adapter start lines, e.g., "Ethernet adapter Ethernet:"
return re.match(r"^[^\s].*adapter.*:", line, re.IGNORECASE)
def _initialize_adapter(adapter_name):
adapter_name_split = adapter_name.split(" adapter ", 1)
if len(adapter_name_split) > 1:
adapter_type = adapter_name_split[0]
adapter_short_name = adapter_name_split[1]
else:
adapter_type = None
adapter_short_name = adapter_name
# Initialize adapter dictionary with all fields set to None or empty lists
return {
"name_long": adapter_name,
"name": adapter_short_name,
"type": adapter_type,
"connection_specific_dns_suffix": None,
"connection_specific_dns_suffix_search_list": [],
"description": None,
"physical_address": None,
"dhcp_enabled": None,
"autoconfiguration_enabled": None,
"ipv6_addresses": [],
"temporary_ipv6_addresses": [],
"link_local_ipv6_addresses": [],
"ipv4_addresses": [],
"default_gateways": [],
"dhcp_server": None,
"dhcpv6_iaid": None,
"dhcpv6_client_duid": None,
"dns_servers": [],
"primary_wins_server": None,
"lease_expires": None,
"lease_obtained": None,
"netbios_over_tcpip": None,
"media_state": None,
"extras": [] # To store unrecognized fields
}
def _parse_line(line):
# Split the line into key and value using ':' or multiple spaces
key_value = re.split(r":", line.strip(), 1)
if len(key_value) == 2:
key, value = key_value
key = key.strip().rstrip('. ')
key = re.sub(r'[^\w]+', '_', key.lower())
value = value.strip() if value.strip() != "" else None
return key, value
else:
return None, None
def _parse_header_line(result, key, value, line_iter):
if key in ["host_name", "primary_dns_suffix", "node_type", "ip_routing_enabled", "wins_proxy_enabled"]:
result[key] = value
elif key == "dns_suffix_search_list":
if value:
result["dns_suffix_search_list"].append(value)
# Process additional entries
_parse_additional_entries(result["dns_suffix_search_list"], line_iter)
else:
# Store unrecognized fields in extras
result["extras"].append({key: value})
def _parse_adapter_line(adapter, key, value, line_iter):
if key in ["connection_specific_dns_suffix","media_state", "description", "physical_address", "dhcp_enabled",
"autoconfiguration_enabled", "dhcpv6_iaid", "dhcpv6_client_duid", "netbios_over_tcpip", "dhcp_server",
"lease_obtained", "lease_expires", "primary_wins_server"]:
adapter[key] = value
elif key in ["ipv6_address", "temporary_ipv6_address", "link_local_ipv6_address"]:
address_dict = _parse_ipv6_address(value)
if key == "ipv6_address":
adapter["ipv6_addresses"].append(address_dict)
elif key == "temporary_ipv6_address":
adapter["temporary_ipv6_addresses"].append(address_dict)
elif key == "link_local_ipv6_address":
adapter["link_local_ipv6_addresses"].append(address_dict)
elif key in ["ipv4_address", "autoconfiguration_ipv4_address", "ip_address", "autoconfiguration_ip_address"]:
ipv4_address_dict = _parse_ipv4_address(value, key, line_iter)
adapter["ipv4_addresses"].append(ipv4_address_dict)
elif key == "connection_specific_dns_suffix_search_list":
if value:
adapter["connection_specific_dns_suffix_search_list"].append(value)
# Process additional connection specific dns suffix search list entries
_parse_additional_entries(adapter["connection_specific_dns_suffix_search_list"], line_iter)
elif key == "default_gateway":
if value:
adapter["default_gateways"].append(value)
# Process additional gateways
_parse_additional_entries(adapter["default_gateways"], line_iter)
elif key == "dns_servers":
if value:
adapter["dns_servers"].append(value)
# Process additional DNS servers
_parse_additional_entries(adapter["dns_servers"], line_iter)
elif key == "subnet_mask":
# Subnet Mask should be associated with the last IPv4 address
if adapter["ipv4_addresses"]:
adapter["ipv4_addresses"][-1]["subnet_mask"] = value
else:
# Store unrecognized fields in extras
adapter["extras"].append({key: value})
def _parse_ipv6_address(value):
# Handle multiple status indicators
match = re.match(r"([^\(]+)\((.*)\)", value) if value else None
if match:
address = match.group(1).strip()
status = match.group(2).strip('()')
else:
address = value
status = None
return {
"address": address,
"status": status
}
def _parse_ipv4_address(value, key, line_iter):
# Handle autoconfigured status
match = re.match(r"([^\(]+)\((.*)\)", value) if value else None
if match:
address = match.group(1).strip()
status = match.group(2).strip('()')
autoconfigured = 'autoconfigured' if 'autoconfiguration' in key or 'autoconfigured' in status else None
else:
address = value
status = None
autoconfigured = 'autoconfigured' if 'autoconfiguration' in key else None
# Get subnet mask
subnet_mask = None
# Peek ahead for "Subnet Mask" line
try:
next_line = next(line_iter)
next_key, next_value = _parse_line(next_line)
if next_key == "subnet_mask":
subnet_mask = next_value
else:
# If it's not "Subnet Mask", put it back into the iterator
line_iter.pushback(next_line)
except StopIteration:
pass
return {
"address": address,
"subnet_mask": subnet_mask,
"autoconfigured": autoconfigured,
"status": status
}
def _parse_additional_entries(entry_list, line_iter):
# Process additional lines that belong to the current entry (e.g., additional DNS servers, DNS Suffix Search List)
while True:
try:
next_line = next(line_iter)
if not next_line.strip():
continue # Skip empty lines
# Check if the line is indented (starts with whitespace)
if re.match(r"^\s\s\s\s", next_line):
# It's an indented line; append the stripped line to entry_list
entry_list.append(next_line.strip())
else:
# Not an indented line; push it back and exit
line_iter.pushback(next_line)
break
except StopIteration:
break

View File

@@ -20,6 +20,9 @@ Schema:
[
{
"chain": string,
"default_policy": string,
"default_packets": integer,
"default_bytes": integer,
"rules": [
{
"num" integer,
@@ -44,6 +47,9 @@ Examples:
[
{
"chain": "PREROUTING",
"default_policy": "DROP",
"default_packets": 0,
"default_bytes": 0,
"rules": [
{
"num": 1,
@@ -103,6 +109,9 @@ Examples:
[
{
"chain": "PREROUTING",
"default_policy": "DROP",
"default_packets": "0",
"default_bytes": "0",
"rules": [
{
"num": "1",
@@ -158,12 +167,13 @@ Examples:
...
]
"""
import re
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.11'
version = '1.12'
description = '`iptables` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -174,6 +184,17 @@ class info():
__version__ = info.version
chain_pkt_byt_pattern = re.compile(
r'''
\s\(policy\s
(?P<policy_name>.+)
\s
(?P<packets>.+)
\spackets,\s
(?P<bytes>.+)
\sbytes\)
''', re.VERBOSE
)
def _process(proc_data):
"""
@@ -188,6 +209,13 @@ def _process(proc_data):
List of Dictionaries. Structured data to conform to the schema.
"""
for entry in proc_data:
if 'default_packets' in entry:
entry['default_packets'] = jc.utils.convert_to_int(entry['default_packets'])
if 'default_bytes' in entry:
entry['default_bytes'] = jc.utils.convert_size_to_int(entry['default_bytes'])
for rule in entry['rules']:
int_list = ['num', 'pkts']
for key in rule:
@@ -243,6 +271,14 @@ def parse(data, raw=False, quiet=False):
parsed_line = line.split()
chain['chain'] = parsed_line[1]
stats_match = re.search(chain_pkt_byt_pattern, line)
if stats_match:
stats = stats_match.groupdict()
chain['default_policy'] = stats['policy_name']
chain['default_packets'] = stats['packets']
chain['default_bytes'] = stats['bytes']
chain['rules'] = []
continue

View File

@@ -122,7 +122,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '0.7'
version = '0.75'
description = '`iw dev [device] scan` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -298,6 +298,7 @@ def parse(data, raw=False, quiet=False):
raw_output = []
section = {}
header = ''
if jc.utils.has_data(data):
@@ -323,7 +324,9 @@ def parse(data, raw=False, quiet=False):
split_line[0] = split_line[0].lower().replace('*', '').replace('(', '')\
.replace(')', '').replace(',', '').replace('-', '_')\
.strip().replace(' ', '_')
section[split_line[0]] = split_line[1].strip()
if split_line[1] == '':
header = split_line[0] + '_'
section[header + split_line[0]] = split_line[1].strip()
continue

View File

@@ -17,46 +17,53 @@ Schema:
[
{
"name": string,
"maj_min": string,
"rm": boolean,
"size": string,
"ro": boolean,
"type": string,
"mountpoint": string,
"kname": string,
"fstype": string,
"label": string,
"uuid": string,
"partlabel": string,
"partuuid": string,
"ra": integer,
"model": string,
"serial": string,
"state": string,
"owner": string,
"group": string,
"mode": string,
"alignment": integer,
"min_io": integer,
"opt_io": integer,
"phy_sec": integer,
"log_sec": integer,
"rota": boolean,
"sched": string,
"rq_size": integer,
"disc_aln": integer,
"disc_gran": string,
"disc_max": string,
"disc_zero": boolean,
"wsame": string,
"wwn": string,
"rand": boolean,
"pkname": string,
"hctl": string,
"tran": string,
"rev": string,
"vendor": string
"name": string,
"maj_min": string,
"rm": boolean,
"size": string,
"size_bytes": integer
"ro": boolean,
"type": string,
"mountpoint": string,
"mountpoints": [
string
],
"kname": string,
"fstype": string,
"label": string,
"uuid": string,
"partlabel": string,
"partuuid": string,
"ra": integer,
"model": string,
"serial": string,
"state": string,
"owner": string,
"group": string,
"mode": string,
"alignment": integer,
"min_io": integer,
"opt_io": integer,
"phy_sec": integer,
"log_sec": integer,
"rota": boolean,
"sched": string,
"rq_size": integer,
"disc_aln": integer,
"disc_gran": string,
"disc_gran_bytes": integer,
"disc_max": string,
"disc_max_bytes": integer,
"disc_zero": boolean,
"wsame": string,
"wsame_bytes": integer,
"wwn": string,
"rand": boolean,
"pkname": string,
"hctl": string,
"tran": string,
"rev": string,
"vendor": string
}
]
@@ -69,6 +76,7 @@ Examples:
"maj_min": "8:0",
"rm": false,
"size": "20G",
"size_bytes": 20000000000,
"ro": false,
"type": "disk",
"mountpoint": null
@@ -78,6 +86,7 @@ Examples:
"maj_min": "8:1",
"rm": false,
"size": "1G",
"size_bytes": 1000000000
"ro": false,
"type": "part",
"mountpoint": "/boot"
@@ -95,6 +104,7 @@ Examples:
"maj_min": "8:0",
"rm": false,
"size": "20G",
"size_bytes": 20000000000,
"ro": false,
"type": "disk",
"mountpoint": null,
@@ -121,9 +131,12 @@ Examples:
"rq_size": 128,
"disc_aln": 0,
"disc_gran": "0B",
"disc_gran_bytes": 0,
"disc_max": "0B",
"disc_max_bytes": 0,
"disc_zero": false,
"wsame": "32M",
"wsame_bytes": 32000000,
"wwn": null,
"rand": true,
"pkname": null,
@@ -137,6 +150,7 @@ Examples:
"maj_min": "8:1",
"rm": false,
"size": "1G",
"size_bytes": 1000000000
"ro": false,
"type": "part",
"mountpoint": "/boot",
@@ -163,9 +177,12 @@ Examples:
"rq_size": 128,
"disc_aln": 0,
"disc_gran": "0B",
"disc_gran_bytes": 0,
"disc_max": "0B",
"disc_max_bytes": 0,
"disc_zero": false,
"wsame": "32M",
"wsame_bytes": 32000000,
"wwn": null,
"rand": true,
"pkname": "sda",
@@ -187,6 +204,7 @@ Examples:
"maj_min": "8:0",
"rm": "0",
"size": "20G",
"size_bytes": 20000000000,
"ro": "0",
"type": "disk",
"mountpoint": null,
@@ -213,9 +231,12 @@ Examples:
"rq_size": "128",
"disc_aln": "0",
"disc_gran": "0B",
"disc_gran_bytes": 0,
"disc_max": "0B",
"disc_max_bytes": 0,
"disc_zero": "0",
"wsame": "32M",
"wsame_bytes": 32000000,
"wwn": null,
"rand": "1",
"pkname": null,
@@ -229,6 +250,7 @@ Examples:
"maj_min": "8:1",
"rm": "0",
"size": "1G",
"size_bytes": 1000000000
"ro": "0",
"type": "part",
"mountpoint": "/boot",
@@ -255,9 +277,12 @@ Examples:
"rq_size": "128",
"disc_aln": "0",
"disc_gran": "0B",
"disc_gran_bytes": 0,
"disc_max": "0B",
"disc_max_bytes": 0,
"disc_zero": "0",
"wsame": "32M",
"wsame_bytes": 32000000,
"wwn": null,
"rand": "1",
"pkname": "sda",
@@ -275,7 +300,7 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.9'
version = '1.10'
description = '`lsblk` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -304,14 +329,19 @@ def _process(proc_data):
int_list = {'ra', 'alignment', 'min_io', 'opt_io', 'phy_sec', 'log_sec',
'rq_size', 'disc_aln'}
size_list = {'size', 'disc_gran', 'disc_max', 'wsame'}
for entry in proc_data:
for key in entry:
for key in entry.copy():
if key in bool_list:
entry[key] = jc.utils.convert_to_bool(entry[key])
if key in int_list:
entry[key] = jc.utils.convert_to_int(entry[key])
if key in size_list:
entry[key + '_bytes'] = jc.utils.convert_size_to_int(entry[key], posix_mode=True)
return proc_data
@@ -335,6 +365,7 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, data.splitlines()))
raw_output = []
new_list = []
if jc.utils.has_data(data):
@@ -346,11 +377,23 @@ def parse(data, raw=False, quiet=False):
raw_output = jc.parsers.universal.sparse_table_parse(cleandata)
# clean up non-ascii characters, if any
# find multiple mount points and add to a single entry
for entry in raw_output:
entry['name'] = entry['name'].encode('ascii', errors='ignore').decode()
if entry['name']:
if 'mountpoints' in entry:
if entry['mountpoints']:
entry['mountpoints'] = [entry['mountpoints']]
else:
entry['mountpoints'] = []
new_list.append(entry)
elif 'mountpoints' in entry and entry['mountpoints']:
new_list[-1]['mountpoints'].append(entry['mountpoints'])
if raw:
return raw_output
else:
return _process(raw_output)
# clean up tree characters, if any
for entry in new_list:
tree_chars = ['`-', '|-', '├─', '└─']
for chars in tree_chars:
if entry['name'][0:2] == chars:
entry['name'] = entry['name'][2:]
return new_list if raw else _process(new_list)

View File

@@ -71,13 +71,12 @@ Example:
]
"""
import re
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.9'
version = '1.11'
description = '`mount` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -111,19 +110,24 @@ def _osx_parse(data):
for entry in data:
output_line = {}
filesystem = entry.split(' on ')
filesystem = filesystem[0]
output_line['filesystem'] = filesystem
pattern = re.compile(
r'''
(?P<filesystem>.*)
\son\s
(?P<mount_point>.*?)
\s
\((?P<options>.*?)\)\s*
''', re.VERBOSE
)
mount_point = entry.split(' on ')
mount_point = mount_point[1].split(' (')
mount_point = mount_point[0]
output_line['mount_point'] = mount_point
mymatch = pattern.match(entry)
groups = mymatch.groupdict()
options = entry.split('(', maxsplit=1)
options = options[1].rstrip(')')
options = options.split(', ')
output_line['options'] = options
if groups:
output_line['filesystem'] = groups['filesystem']
output_line['mount_point'] = groups['mount_point']
options = groups['options'].split(', ')
output_line['options'] = options
output.append(output_line)
@@ -138,16 +142,19 @@ def _linux_parse(data):
pattern = re.compile(
r'''
(?P<filesystem>\S+)\s+
on\s+
(?P<mount_point>.*?)\s+
type\s+
(?P<type>\S+)\s+
\((?P<options>.*?)\)\s*''',
re.VERBOSE)
(?P<filesystem>.*)
\son\s
(?P<mount_point>.*?)
\stype\s
(?P<type>\S+)
\s+
\((?P<options>.*?)\)
\s*
''', re.VERBOSE
)
match = pattern.match(entry)
groups = match.groupdict()
mymatch = pattern.match(entry)
groups = mymatch.groupdict()
if groups:
output_line['filesystem'] = groups["filesystem"]
@@ -215,7 +222,7 @@ def parse(data, raw=False, quiet=False):
# check for OSX and AIX output
if ' type ' not in cleandata[0]:
if 'node' in cleandata[0]:
if ' node ' in cleandata[0]:
raw_output = _aix_parse(cleandata)
else:
raw_output = _osx_parse(cleandata)
@@ -223,7 +230,4 @@ def parse(data, raw=False, quiet=False):
else:
raw_output = _linux_parse(cleandata)
if raw:
return raw_output
else:
return _process(raw_output)
return raw_output if raw else _process(raw_output)

View File

@@ -0,0 +1,194 @@
r"""jc - JSON Convert `net localgroup` command output parser
Usage (cli):
$ net localgroup | jc --net-localgroup
$ net localgroup /domain | jc --net-localgroup
$ net localgroup Administrators | jc --net-localgroup
$ net localgroup Administrators /domain | jc --net-localgroup
Usage (module):
import jc
result = jc.parse('net_localgroup', net_localgroup_command_output)
Schema:
{
"account_origin": string,
"domain": string,
"comment": string,
"groups": [
{
"name": string
"members": [
string
]
}
],
}
Examples:
$ net localgroup | jc --net-localgroup -p
{
"account_origin": null,
"comment": null,
"domain": null,
"groups": [
{
"name": "Administrators",
"members": [
"Administrator",
"Operator",
"ansible",
"user1"
]
}
]
}
"""
import re
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`net localgroup` command parser'
author = 'joehacksalot'
author_email = 'joehacksalot@gmail.com'
compatible = ['win32']
magic_commands = ['net localgroup']
tags = ['command']
__version__ = info.version
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Parsed dictionary. The raw and processed data structures are the same.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output = {}
if jc.utils.has_data(data):
# Initialize the parsed output dictionary with all fields set to None or empty lists
raw_output = _parse(data)
return raw_output if raw else _process(raw_output)
def _process(proc_data):
"""
Final processing to conform to the schema.
Parameters:
proc_data: (Dictionary) raw structured data to process
Returns:
Processed Dictionary. Structured data to conform to the schema.
"""
return proc_data
class _PushbackIterator:
def __init__(self, iterator):
self.iterator = iterator
self.pushback_stack = []
def __iter__(self):
return self
def __next__(self):
if self.pushback_stack:
return self.pushback_stack.pop()
else:
return next(self.iterator)
def pushback(self, value):
self.pushback_stack.append(value)
def _parse_net_localgroup_list(line_iter, expect_asterisk):
name_list = []
while True:
try:
line = next(line_iter)
if not line.strip():
continue # Skip empty lines
# Check if the line starts with an asterisk
if line == 'The command completed successfully.':
break
elif expect_asterisk and line.startswith('*'):
name_list.append(line[1:].strip())
else:
name_list.append(line)
except StopIteration:
break
return name_list
def _parse(data):
lines = data.splitlines()
parse_type = None # Can be 'groups_list' or 'members'
result = {
"account_origin": None,
"domain": None,
"comment": None,
"groups": []
}
group_name = ""
lines = data.splitlines()
lines = [line.rstrip() for line in lines if line.strip() != ""]
line_iter = _PushbackIterator(iter(lines))
for line in line_iter:
line = line.rstrip()
# Skip empty lines
if not line.strip():
continue
match_domain_processed = re.match(r"^The request will be processed at a domain controller for domain (.+)", line, re.IGNORECASE)
match_localgroup_list = re.match(r"^Aliases for[\s]*([^:]+)", line, re.IGNORECASE) # "Aliases for \\DESKTOP-WIN11:"
match_localgroup_members = re.match(r"^Alias name[\s]*([^:]+)", line, re.IGNORECASE) # "Alias name administrators:"
if match_domain_processed:
# Extract the domain name
result["domain"] = match_domain_processed.group(1).strip()
elif match_localgroup_list:
# Extract the account origin
result["account_origin"] = match_localgroup_list.group(1).strip()
parse_type = 'groups_list' # Prepare to read groups
elif match_localgroup_members:
# We are querying a specific group
group_name = match_localgroup_members.group(1).strip()
parse_type = 'members_list' # Prepare to read members
elif line.startswith('Comment'):
comment_line = line.split('Comment', 1)[1].strip()
result["comment"] = comment_line if comment_line else None
elif line.startswith('---'):
# Start of a section (groups or members)
if parse_type == 'groups_list':
names_list = _parse_net_localgroup_list(line_iter, expect_asterisk=True)
result["groups"] = [{"name": group_name, "members": []} for group_name in names_list]
elif parse_type == 'members_list':
names_list = _parse_net_localgroup_list(line_iter, expect_asterisk=False)
result["groups"] = [{
"name": group_name,
"members": names_list
}]
return result

441
jc/parsers/net_user.py Normal file
View File

@@ -0,0 +1,441 @@
r"""jc - JSON Convert `net user` command output parser
Usage (cli):
$ net users | jc --net-user
$ net users /domain | jc --net-user
$ net users User1 | jc --net-user
$ net users User1 /domain | jc --net-user
Usage (module):
import jc
result = jc.parse('net_user', net_user_command_output)
Schema:
{
"domain": string,
"account_origin": string,
"user_accounts": [
{
"user_name": string,
"full_name": string,
"comment": string,
"user_comment": string,
"country_region_code": string,
"account_active": boolean,
"account_expires": string,
"password_last_set": string,
"password_expires": string,
"password_changeable": string,
"password_required": boolean,
"user_may_change_password": boolean,
"workstations_allowed": string,
"logon_script": string,
"user_profile": string,
"home_directory": string,
"last_logon": string,
"logon_hours_allowed": string,
"local_group_memberships": [
string,
],
"global_group_memberships": [
string,
]
}
]
}
Examples:
$ net users | jc --net-user -p
{
"account_origin": "\\\\WIN-SERVER16",
"domain": "",
"user_accounts": [
{
"user_name": "Administrator"
},
{
"user_name": "DefaultAccount"
},
{
"user_name": "Guest"
},
{
"user_name": "pentera_BnlLQVnd7p"
},
{
"user_name": "user1"
}
]
}
$ net users /domain | jc --net-user -p
{
"account_origin": "\\\\DESKTOP-WIN10-PRO.somecompany.corp",
"domain": "somecompany.corp",
"user_accounts": [
{
"user_name": "aaron"
},
{
"user_name": "addison"
},
{
"user_name": "Administrator"
},
{
"user_name": "ansible"
},
{
"user_name": "da"
},
{
"user_name": "DefaultAccount"
},
{
"user_name": "Guest"
},
{
"user_name": "harrison"
},
{
"user_name": "james"
},
{
"user_name": "krbtgt"
},
{
"user_name": "liam"
},
{
"user_name": "localadmin"
},
{
"user_name": "tiffany"
}
]
}
$ net users Administrator | jc --net-user -p
{
"domain": "",
"user_accounts": [
{
"account_active": true,
"account_expires": "Never",
"comment": "Built-in account for administering the computer/domain",
"country_region_code": "000 (System Default)",
"global_group_memberships": [],
"last_logon": "2024-08-23T13:47:11",
"local_group_memberships": [
"Administrators"
],
"logon_hours_allowed": "All",
"password_changeable": "2021-12-17T11:07:14",
"password_expires": "2022-01-27T11:07:14",
"password_last_set": "2021-12-16T11:07:14",
"password_required": true,
"user_may_change_password": true,
"user_name": "Administrators",
"workstations_allowed": "All"
}
]
}
$ net users Administrator /domain | jc --net-user -p | jq
{
"domain": "somecompany.corp",
"user_accounts": [
{
"account_active": true,
"account_expires": "Never",
"comment": "Built-in account for administering the computer/domain",
"country_region_code": "000 (System Default)",
"global_group_memberships": [
"Domain Admins",
"Domain Users",
"Group Policy Creator",
"Enterprise Admins",
"Schema Admins"
],
"last_logon": "2024-07-17T13:46:12",
"local_group_memberships": [
"Administrators"
],
"logon_hours_allowed": "All",
"password_changeable": "2023-09-30T11:44:26",
"password_expires": "Never",
"password_last_set": "2023-09-29T11:44:26",
"password_required": true,
"user_may_change_password": true,
"user_name": "Administrators",
"workstations_allowed": "All"
}
]
}
"""
from datetime import datetime
import re
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`net user` command parser'
author = 'joehacksalot'
author_email = 'joehacksalot@gmail.com'
compatible = ['win32']
magic_commands = ['net user']
tags = ['command']
__version__ = info.version
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Parsed dictionary. The raw and processed data structures are the same.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output = {}
if jc.utils.has_data(data):
raw_output = _parse(data)
if not raw_output:
raw_output = {}
return raw_output if raw else _process(raw_output)
def _set_if_not_none(output_dict, key, value):
if value is not None:
output_dict[key] = value
def _process_string_is_yes(text):
if text:
return text.lower() == "yes"
else:
return None
def _process_date(s):
if s is not None:
for fmt in ('%m/%d/%Y %I:%M:%S %p', '%m/%d/%Y %H:%M:%S %p'):
try:
return datetime.strptime(s, fmt).isoformat()
except ValueError:
continue
return s # Return the original string if parsing fails
def _process(proc_data):
"""
Final processing to conform to the schema.
Parameters:
proc_data: (Dictionary) raw structured data to process
Returns:
Processed Dictionary. Structured data to conform to the schema.
"""
for user_account in proc_data.get("user_accounts", []):
_set_if_not_none(user_account, "account_active", _process_string_is_yes(user_account.get("account_active", None)))
_set_if_not_none(user_account, "password_last_set", _process_date(user_account.get("password_last_set", None)))
_set_if_not_none(user_account, "password_expires", _process_date(user_account.get("password_expires", None)))
_set_if_not_none(user_account, "password_changeable", _process_date(user_account.get("password_changeable", None)))
_set_if_not_none(user_account, "password_required", _process_string_is_yes(user_account.get("password_required", None)))
_set_if_not_none(user_account, "user_may_change_password", _process_string_is_yes(user_account.get("user_may_change_password", None)))
_set_if_not_none(user_account, "last_logon", _process_date(user_account.get("last_logon", None)))
if not proc_data:
proc_data = {}
return proc_data
class _PushbackIterator:
def __init__(self, iterator):
self.iterator = iterator
self.pushback_stack = []
def __iter__(self):
return self
def __next__(self):
if self.pushback_stack:
return self.pushback_stack.pop()
else:
return next(self.iterator)
def pushback(self, value):
self.pushback_stack.append(value)
def _parse_user_account_keypairs(keypair_dict):
user_account_parsed = {}
_set_if_not_none(user_account_parsed, "user_name", keypair_dict.get("user_name", None))
_set_if_not_none(user_account_parsed, "full_name", keypair_dict.get("full_name", None))
_set_if_not_none(user_account_parsed, "comment", keypair_dict.get("comment", None))
_set_if_not_none(user_account_parsed, "users_comment", keypair_dict.get("users_comment", None))
_set_if_not_none(user_account_parsed, "country_region_code", keypair_dict.get("country_region_code", None))
_set_if_not_none(user_account_parsed, "account_active", keypair_dict.get("account_active", None))
_set_if_not_none(user_account_parsed, "account_expires", keypair_dict.get("account_expires", None))
_set_if_not_none(user_account_parsed, "password_last_set", keypair_dict.get("password_last_set", None))
_set_if_not_none(user_account_parsed, "password_expires", keypair_dict.get("password_expires", None))
_set_if_not_none(user_account_parsed, "password_changeable", keypair_dict.get("password_changeable", None))
_set_if_not_none(user_account_parsed, "password_required", keypair_dict.get("password_required", None))
_set_if_not_none(user_account_parsed, "user_may_change_password", keypair_dict.get("user_may_change_password", None))
_set_if_not_none(user_account_parsed, "workstations_allowed", keypair_dict.get("workstations_allowed", None))
_set_if_not_none(user_account_parsed, "logon_script", keypair_dict.get("logon_script", None))
_set_if_not_none(user_account_parsed, "user_profile", keypair_dict.get("user_profile", None))
_set_if_not_none(user_account_parsed, "home_directory", keypair_dict.get("home_directory", None))
_set_if_not_none(user_account_parsed, "last_logon", keypair_dict.get("last_logon", None))
_set_if_not_none(user_account_parsed, "logon_hours_allowed", keypair_dict.get("logon_hours_allowed", None))
_set_if_not_none(user_account_parsed, "local_group_memberships", keypair_dict.get("local_group_memberships", None))
_set_if_not_none(user_account_parsed, "global_group_memberships", keypair_dict.get("global_group_memberships", None))
return user_account_parsed
def _parse_groups(line_iter):
group_list = []
# Process additional lines that belong to the current entry (e.g., additional DNS servers, DNS Suffix Search List)
while True:
try:
next_line = next(line_iter).strip()
if not next_line:
continue # Skip empty lines
# Check if the line is indented (starts with whitespace)
if next_line.startswith('*'):
groups = next_line.split("*")
groups = [group.strip() for group in groups if group.strip() != ""]
if "None" in groups:
groups.remove("None")
# It's an indented line; append the stripped line to entry_list
group_list.extend(groups)
else:
# Not an indented line; push it back and exit
line_iter.pushback(next_line)
break
except StopIteration:
break
return group_list
def _parse(data):
result = {
"domain": "",
"user_accounts": []
}
lines = data.splitlines()
lines = [line.rstrip() for line in lines if line.strip() != ""]
line_iter = _PushbackIterator(iter(lines))
for line in line_iter:
line = line.rstrip()
# Skip empty lines
if not line.strip():
continue
match_domain_processed = re.match(r"^The request will be processed at a domain controller for domain (.+)\.$", line, re.IGNORECASE)
if match_domain_processed:
result["domain"] = match_domain_processed.group(1).strip()
# Check if the text is of the first type (detailed user info)
elif "User name" in line:
line_iter.pushback(line)
user_account_keypairs = {}
# Regular expression to match key-value pairs
kv_pattern = re.compile(r'^([\w\s\/\'\-]{1,29})\s*(.+)?$')
key = None
while True:
# Process each line
# Break when done
try:
line = next(line_iter)
line = line.strip()
if not line:
continue # Skip empty lines
match = kv_pattern.match(line)
if "The command completed" in line:
break
elif match:
key_raw = match.group(1).strip()
key = key_raw.lower().replace(" ", "_").replace("'", "").replace("/", "_").replace("-", "_")
if len(match.groups()) == 2 and match.group(2) is not None:
value = match.group(2).strip()
if key in ["local_group_memberships", "global_group_memberships"]:
line_iter.pushback(value)
user_account_keypairs[key] = _parse_groups(line_iter)
else:
user_account_keypairs[key] = value
else:
# Line without value, it's a key with empty value
user_account_keypairs[key] = None
else:
raise ValueError(f"Unexpected line: {line}")
except StopIteration:
break
# Convert specific fields
result["user_accounts"].append(_parse_user_account_keypairs(user_account_keypairs))
elif "User accounts for" in line:
line_iter.pushback(line)
collecting_users = False
while True:
# Process each line
# Break when done
try:
line = next(line_iter)
line = line.strip()
if not line:
continue # Skip empty lines
# Check for domain line
domain_pattern = re.compile(r'^User accounts for (.+)$')
account_origin_match = domain_pattern.match(line)
if account_origin_match:
result["account_origin"] = account_origin_match.group(1)
continue
# Check for the line of dashes indicating start of user list
if line.startswith('---'):
collecting_users = True
continue
# Check for the completion message
if line.startswith('The command completed'):
break
if collecting_users:
# Split the line into usernames
user_matches = re.match(r'(.{1,20})(\s+.{1,20})?(\s+.{1,20})?', line)
if user_matches:
for username in user_matches.groups():
if username:
username = username.strip()
user_account = {"user_name": username}
result["user_accounts"].append(user_account)
except StopIteration:
break
else:
raise ValueError(f"Unexpected line: {line}")
return result

View File

@@ -355,7 +355,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.15'
version = '1.16'
description = '`netstat` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'

View File

@@ -1,5 +1,6 @@
r"""jc - JSON Convert Linux netstat Parser"""
import string
import re
def normalize_headers(header):
@@ -38,7 +39,7 @@ def parse_network(headers, entry):
]
# split entry based on presence of value in "State" column
contains_state = any(state in entry for state in LIST_OF_STATES)
contains_state = any(re.search(rf"\b{re.escape(state)}\b", entry) for state in LIST_OF_STATES)
split_modifier = 1 if contains_state else 2
entry = entry.split(maxsplit=len(headers) - split_modifier)

View File

@@ -36,6 +36,8 @@ These are documented below.
[
{
"<key>": string/integer/float, # [0]
"team_config": object/null,
"team_port_config": object/null,
"dhcp4_option_x": {
"name": string,
"value": string/integer/float,
@@ -141,6 +143,7 @@ Examples:
]
"""
import re
import json
from typing import List, Dict, Optional
import jc.utils
from jc.parsers.universal import sparse_table_parse
@@ -149,7 +152,7 @@ from jc.exceptions import ParseError
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
version = '1.2'
description = '`nmcli` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -313,8 +316,39 @@ def _device_show_parse(data: str) -> List[Dict]:
def _connection_show_x_parse(data: str) -> List[Dict]:
raw_output: List = []
item: Dict = {}
in_team_config: bool = False
team_config_value: List = []
for line in filter(None, data.splitlines()):
# fix for team.config and team-port.config, which are multi-line JSON
if line.startswith('team.config:') or line.startswith('team-port.config:'):
key, value = line.split(':', maxsplit=1)
key_team = _normalize_key(key)
value_team = value.strip()
if value_team == '--':
team_config_value = []
item[key_team] = None
continue
in_team_config = True
team_config_value.append(value_team)
item[key_team] = {}
continue
STARTSWITH_TEAM = line.startswith('team.') or line.startswith('team-port.')
if not STARTSWITH_TEAM and in_team_config:
team_config_value.append(line.strip())
continue
in_team_config = False
if team_config_value:
# team.config and team-port.config values should always be JSON
item[key_team] = json.loads(''.join(team_config_value))
team_config_value = []
key, value = line.split(':', maxsplit=1)
key_n = _normalize_key(key)

View File

@@ -17,20 +17,24 @@ Schema:
[
{
"version": string,
"verbosity": integer,
"ratelimit": integer
"version": string,
"verbosity": integer,
"ratelimit": integer
}
]
[
{
"zone": string
"zone": string
"status": {
"state": string,
"served-serial": string,
"commit-serial": string,
"wait": string
"state": string,
"pattern": string, # Additional
"catalog-member-id": string, # Additional
"served-serial": string,
"commit-serial": string,
"notified-serial": string, # Conditional
"wait": string,
"transfer": string # Conditional
}
}
]
@@ -65,7 +69,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.1'
version = '1.2'
description = '`nsd-control` command parser'
author = 'Pettai'
author_email = 'pettai@sunet.se'
@@ -89,7 +93,7 @@ def _process(proc_data):
List of Dictionaries. Structured to conform to the schema.
"""
int_list = {'verbosity', 'ratelimit', 'wait'}
int_list = {'verbosity', 'ratelimit', 'wait', 'transfer'}
for entry in proc_data:
for key in entry:
@@ -229,6 +233,20 @@ def parse(data: str, raw: bool = False, quiet: bool = False):
raw_output.append(zonename)
continue
if line.startswith('notified-serial:'):
linedata = line.split(': ', maxsplit=1)
notified = linedata[1].strip('"').rstrip('"')
zstatus.update({'notified-serial': notified})
continue
if line.startswith('transfer:'):
linedata = line.split(': ', maxsplit=1)
transfer = linedata[1].strip('"').rstrip('"')
zstatus.update({'transfer': transfer})
zonename.update({'status': zstatus})
raw_output.append(zonename)
continue
# stats
if line.startswith('server') or line.startswith('num.') or line.startswith('size.') or line.startswith('time.') or line.startswith('zone.'):
itrparse = True

348
jc/parsers/pacman.py Normal file
View File

@@ -0,0 +1,348 @@
r"""jc - JSON Convert `pacman` command output parser
Supports the following `pacman` arguments:
- `-Si`
- `-Sii`
- `-Qi`
- `-Qii`
The `*_epoch` calculated timestamp fields are naive. (i.e. based on the
local time of the system the parser is run on)
Usage (cli):
$ pacman -Si <package> | jc --pacman
or
$ jc pacman -Si <package>
Usage (module):
import jc
result = jc.parse('pacman', pacman_command_output)
Schema:
[
{
"repository": string,
"name": string,
"version": string,
"description": string,
"architecture": string,
"url": string,
"licenses": [
string
],
"groups": [
string
],
"provides": [
string
],
"depends_on": [
string
],
"optional_deps": [
{
"name": string,
"description": string
}
],
"optional_for": [
string
],
"conflicts_with": [
string
],
"replaces": [
string
],
"download_size": string,
"download_size_bytes": integer [0]
"installed_size": string,
"installed_size_bytes": integer, [0]
"packager": string,
"build_date": string,
"build_date_epoch": integer, [0]
"install_date": string,
"install_date_epoch": integer, [0]
"validated_by": [
string
],
"backup_files": [
string
]
}
]
[0] Field exists if conversion successful
Examples:
$ pacman -qii zstd | jc --pacman -p
[
{
"name": "zstd",
"version": "1.5.6-1",
"description": "Zstandard - Fast real-time compression algorithm",
"architecture": "x86_64",
"url": "https://facebook.github.io/zstd/",
"licenses": [
"BSD-3-Clause",
"GPL-2.0-only"
],
"groups": [],
"provides": [
"libzstd.so=1-64"
],
"depends_on": [
"glibc",
"gcc-libs",
"zlib",
"xz",
"lz4"
],
"required_by": [
"android-tools",
"appstream",
...
"tiled",
"vulkan-radeon",
"wireshark-cli"
],
"optional_for": [
"xarchiver"
],
"conflicts_with": [],
"replaces": [],
"installed_size": "1527.00 KiB",
"installed_size_bytes": 1563648,
"packager": "Levente Polyak <anthraxx@archlinux.org>",
"build_date": "Sat 11 May 2024 06:14:19 AM +08",
"build_date_epoch": 1715433259,
"install_date": "Fri 24 May 2024 09:50:31 AM +08",
"install_date_epoch": 1715663342,
"install_reason": "Installed as a dependency for another package",
"install_script": "No",
"validated_by": [
"Signature"
],
"extended_data": "pkgtype=pkg"
}
]
$ pacman -qii zstd | jc --pacman -p -r
[
{
"name": "zstd",
"version": "1.5.6-1",
"description": "Zstandard - Fast real-time compression algorithm",
"architecture": "x86_64",
"url": "https://facebook.github.io/zstd/",
"licenses": "BSD-3-Clause GPL-2.0-only",
"groups": null,
"provides": "libzstd.so=1-64",
"depends_on": "glibc gcc-libs zlib xz lz4",
"required_by": [
"android-tools appstream avr-gcc binutils blender blosc",
"boost-libs btrfs-progs cloudflare-warp-bin comgr curl",
"dolphin-emu file flatpak gcc gdal gnutls karchive",
"karchive5 kmod lib32-zstd libarchive libelf libtiff",
"libva-mesa-driver libxmlb libzip lld llvm-libs mariadb-libs",
"mesa mesa-vdpau minizip-ng mkinitcpio mold netcdf",
"opencl-clover-mesa opencl-rusticl-mesa openucx postgresql",
"postgresql-libs ppsspp qemu-img qemu-system-riscv",
"qemu-system-x86 qgis qt6-base qt6-tools rsync rustup",
"squashfs-tools squashfuse systemd-libs tiled vulkan-radeon",
"wireshark-cli"
],
"optional_for": "xarchiver",
"conflicts_with": null,
"replaces": null,
"installed_size": "1527.00 KiB",
"packager": "Levente Polyak <anthraxx@archlinux.org>",
"build_date": "Sat 11 May 2024 06:14:19 AM +08",
"install_date": "Fri 24 May 2024 09:50:31 AM +08",
"install_reason": "Installed as a dependency for another package",
"install_script": "No",
"validated_by": "Signature",
"extended_data": "pkgtype=pkg"
}
]
"""
from typing import List, Dict
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.1'
description = '`pacman` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['command', 'file']
magic_commands = ['pacman', 'yay']
__version__ = info.version
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.
"""
split_fields = {
'licenses', 'groups', 'provides', 'depends_on', 'conflicts_with',
'replaces', 'optional_for'
}
space_split_fields = {
'required_by', 'groups', 'provides', 'depends_on',
'conflicts_with', 'replaces', 'validated_by'
}
two_space_fields = {'licenses', 'validated_by'}
name_description_fields = {'optional_deps'}
size_fields = {'download_size', 'installed_size'}
date_fields = {'build_date', 'install_date'}
# initial split for field lists
for item in proc_data:
for key, val in item.copy().items():
if key in split_fields:
if val is None:
item[key] = []
else:
item[key] = val.split()
# fixup for specific lists
if key in space_split_fields and isinstance(val, List):
val_list = [x.split() for x in val]
item[key] = [x for xs in val_list for x in xs] # flatten the list
if key in two_space_fields and isinstance(val, str):
item[key] = val.split(' ')
if key in name_description_fields and isinstance(val, list):
new_list = []
for name_desc in val:
n, *d = name_desc.split(': ')
if d == []:
d = ''
else:
d = d[0]
new_obj = {'name': n, 'description': d}
new_list.append(new_obj)
item[key] = new_list
if key in size_fields:
bts = jc.utils.convert_size_to_int(val)
if bts:
item[key + '_bytes'] = bts
if key in date_fields:
# need to append '00' to date for conversion
ts = jc.utils.timestamp(val + '00', format_hint=(3100,))
if ts.naive:
item[key + '_epoch'] = ts.naive
else:
# try taking off the text TZ identifier
ts = jc.utils.timestamp(val[:-4], format_hint=(3000,))
if ts.naive:
item[key + '_epoch'] = ts.naive
return proc_data
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> List[JSONDictType]:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List[Dict] = []
entry_obj: Dict = {}
multiline_fields = {'required_by', 'optional_deps', 'backup_files'}
multiline_list: List = []
multiline_key = ''
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
splitline = line.split(' : ', maxsplit=1)
if len(splitline) == 2:
# this is a key/value pair
key, val = splitline
key = key.strip()
key = jc.utils.normalize_key(key)
val = val.strip()
# new entries can start with "Repository" or "Name"
if (key == 'name' or key == 'repository') and len(entry_obj) > 2:
if multiline_list:
entry_obj[multiline_key] = multiline_list
multiline_list = []
multiline_key = ''
if entry_obj:
raw_output.append(entry_obj)
entry_obj = {}
entry_obj[key] = val
continue
if key in multiline_fields:
if multiline_list:
entry_obj[multiline_key] = multiline_list
multiline_list = []
if val != 'None':
multiline_list.append(val)
multiline_key = key
continue
if key not in multiline_fields:
if multiline_list:
entry_obj[multiline_key] = multiline_list
multiline_list = []
multiline_key = ''
entry_obj[key] = val if val != 'None' else None
continue
# multiline field continuation lines
multiline_list.append(line.strip())
continue
# grab the last entry
if entry_obj:
if multiline_list:
entry_obj[multiline_key] = multiline_list
multiline_list = []
multiline_key = ''
raw_output.append(entry_obj)
return raw_output if raw else _process(raw_output)

View File

@@ -88,7 +88,7 @@ from jc.exceptions import ParseError
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.5'
version = '1.6'
description = '`ping` and `ping6` command streaming parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -169,7 +169,7 @@ def _ipv6_in(line):
return ipv6
def _error_type(line):
def _error_type_v4(line):
# from https://github.com/dgibson/iputils/blob/master/ping.c
# https://android.googlesource.com/platform/external/ping/+/8fc3c91cf9e7f87bc20b9e6d3ea2982d87b70d9a/ping.c
# https://opensource.apple.com/source/network_cmds/network_cmds-328/ping.tproj/ping.c
@@ -207,6 +207,37 @@ def _error_type(line):
return None
def _error_type_v6(line):
type_map = {
'Destination unreachable': 'destination_unreachable',
'Packet too big': 'packet_too_big',
'Time exceeded:': 'time_exceeded',
'Parameter problem:': 'parameter_problem',
}
code_map = {
'destination_unreachable': {
'No route': 'no_route',
'Administratively prohibited': 'administratively_prohibited',
"Beyond scope of source address": 'beyond_scope_of_source_address',
'Address unreachable': 'address_unreachable',
'Port unreachable': 'port_unreachable',
},
'time_exceeded': {
'Hop limit': 'hop_limit',
'Fragment reassembly time exceeded': 'fragment_reassembly_time_exceeded',
},
}
return_code = None
for err_type, code in type_map.items():
if err_type in line:
return_code = code
for err_code, code_name in code_map[code].items():
if err_code in line:
return_code += '_' + code_name
return return_code
def _bsd_parse(line, s):
output_line = {}
@@ -263,6 +294,24 @@ def _bsd_parse(line, s):
# ping response lines
err = None
if s.ipv4:
err = _error_type_v4(line)
else:
err = _error_type_v6(line)
if err:
output_line = {
'type': err
}
try:
output_line['sent_bytes'] = line.split()[0]
output_line['destination_ip'] = s.destination_ip
output_line['response_ip'] = line.split()[4].strip(':').strip('(').strip(')')
except Exception:
pass
return output_line
# ipv4 lines
if not _ipv6_in(line):
@@ -279,7 +328,7 @@ def _bsd_parse(line, s):
return output_line
# catch error responses
err = _error_type(line)
err = _error_type_v4(line)
if err:
output_line = {
'type': err
@@ -444,25 +493,40 @@ def _linux_parse(line, s):
}
return output_line
# if timestamp option is specified, then shift icmp sequence field right by one
timestamp = False
if line[0] == '[':
timestamp = True
timestamp_offset = 1 if timestamp else 0
# ping response lines
err = None
if s.ipv4:
err = _error_type_v4(line)
else:
err = _error_type_v6(line)
if err:
output_line = {
'type': err,
'destination_ip': s.destination_ip or None,
'sent_bytes': s.sent_bytes or None,
'response_ip': line.split()[timestamp_offset + 1] if type != 'timeout' else None,
'icmp_seq': line.replace('=', ' ').split()[timestamp_offset + 3],
'timestamp': line.split()[0].lstrip('[').rstrip(']') if timestamp else None,
}
return output_line
# request timeout
if 'no answer yet for icmp_seq=' in line:
timestamp = False
isequence = 5
# if timestamp option is specified, then shift icmp sequence field right by one
if line[0] == '[':
timestamp = True
isequence = 6
output_line = {
'type': 'timeout',
'destination_ip': s.destination_ip or None,
'sent_bytes': s.sent_bytes or None,
'pattern': s.pattern or None,
'timestamp': line.split()[0].lstrip('[').rstrip(']') if timestamp else None,
'icmp_seq': line.replace('=', ' ').split()[isequence]
'icmp_seq': line.replace('=', ' ').split()[timestamp_offset + 5]
}
return output_line
@@ -473,20 +537,16 @@ def _linux_parse(line, s):
line = line.replace('(', ' ').replace(')', ' ').replace('=', ' ')
# positions of items depend on whether ipv4/ipv6 and/or ip/hostname is used
param_positions = None
if s.ipv4 and not s.hostname:
bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9)
param_positions = (0, 3, 5, 7, 9)
elif s.ipv4 and s.hostname:
bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11)
param_positions = (0, 4, 7, 9, 11)
elif not s.ipv4 and not s.hostname:
bts, rip, iseq, t2l, tms = (0, 3, 5, 7, 9)
param_positions = (0, 3, 5, 7, 9)
elif not s.ipv4 and s.hostname:
bts, rip, iseq, t2l, tms = (0, 4, 7, 9, 11)
# if timestamp option is specified, then shift everything right by one
timestamp = False
if line[0] == '[':
timestamp = True
bts, rip, iseq, t2l, tms = (bts + 1, rip + 1, iseq + 1, t2l + 1, tms + 1)
param_positions = (0, 4, 7, 9, 11)
bts, rip, iseq, t2l, tms = (x + timestamp_offset for x in param_positions)
output_line = {
'type': 'reply',

View File

@@ -26,7 +26,7 @@ or
or
$ cat /proc/meminfo | jc --proc-memifno
$ cat /proc/meminfo | jc --proc-meminfo
Usage (module):

View File

@@ -4,7 +4,6 @@ Edid module
import struct
from collections import namedtuple
from typing import ByteString
__all__ = ["Edid"]
@@ -108,10 +107,10 @@ class Edid:
),
)
def __init__(self, edid: ByteString):
def __init__(self, edid: bytes):
self._parse_edid(edid)
def _parse_edid(self, edid: ByteString):
def _parse_edid(self, edid: bytes):
"""Convert edid byte string to edid object"""
if struct.calcsize(self._STRUCT_FORMAT) != 128:
raise ValueError("Wrong edid size.")

View File

@@ -3,7 +3,7 @@ EDID helper
"""
from subprocess import CalledProcessError, check_output
from typing import ByteString, List
from typing import List
__all__ = ["EdidHelper"]
@@ -12,14 +12,14 @@ class EdidHelper:
"""Class for working with EDID data"""
@staticmethod
def hex2bytes(hex_data: str) -> ByteString:
def hex2bytes(hex_data: str) -> bytes:
"""Convert hex EDID string to bytes
Args:
hex_data (str): hex edid string
Returns:
ByteString: edid byte string
bytes: edid byte string
"""
# delete edid 1.3 additional block
if len(hex_data) > 256:
@@ -32,14 +32,14 @@ class EdidHelper:
return bytes(numbers)
@classmethod
def get_edids(cls) -> List[ByteString]:
def get_edids(cls) -> List[bytes]:
"""Get edids from xrandr
Raises:
`RuntimeError`: if error with retrieving xrandr util data
Returns:
List[ByteString]: list with edids
List[bytes]: list with edids
"""
try:
output = check_output(["xrandr", "--verbose"])

View File

@@ -111,8 +111,6 @@ Examples:
]
"""
import jc.utils
import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""

455
jc/parsers/route_print.py Normal file
View File

@@ -0,0 +1,455 @@
r"""jc - JSON Convert `route print` command output parser
See also: the `route` command parser
Usage (cli):
$ route print | jc --route-print
Usage (module):
import jc
result = jc.parse('route_print', route_print_command_output)
Schema:
{
"interface_list": [
{
"interface_index": integer,
"mac_address": string,
"description": string
}
],
"ipv4_route_table": {
"active_routes": [
{
"network_destination": string,
"netmask": string,
"gateway": string,
"interface": string,
"metric": integer, # [0]
"metric_set_to_default": boolean # [1]
}
],
"persistent_routes": [
{
"network_address": string,
"netmask": string,
"gateway_address": string,
"metric": integer # [0]
"metric_set_to_default": boolean # [1]
}
]
},
"ipv6_route_table": {
"active_routes": [
{
"interface": integer,
"metric": integer, # [0]
"metric_set_to_default": boolean, # [1]
"network_destination": string,
"gateway": string
}
],
"persistent_routes": [
{
"interface": integer,
"metric": integer, # [0]
"metric_set_to_default": boolean, # [1]
"network_destination": string,
"gateway": string
}
]
}
}
[0] Null/None if "metric" = "Default"
[1] True if "metric" = "Default"
Examples:
$ route print | jc --route-print -p
{
"interface_list": [
{
"interface_index": 28,
"mac_address": null,
"description": "Tailscale Tunnel"
},
{
"interface_index": 12,
"mac_address": "00:1c:42:da:01:6a",
"description": "Parallels VirtIO Ethernet Adapter"
},
{
"interface_index": 1,
"mac_address": null,
"description": "Software Loopback Interface 1"
}
],
"ipv4_route_table": {
"active_routes": [
{
"network_destination": "0.0.0.0",
"netmask": "0.0.0.0",
"gateway": "10.211.55.1",
"interface": "10.211.55.3",
"metric": 15,
"metric_set_to_default": false
},
{
"network_destination": "10.0.0.0",
"netmask": "255.0.0.0",
"gateway": "192.168.22.1",
"interface": "10.211.55.3",
"metric": 16,
"metric_set_to_default": false
},
...
{
"network_destination": "255.255.255.255",
"netmask": "255.255.255.255",
"gateway": "On-link",
"interface": "10.211.55.3",
"metric": null,
"metric_set_to_default": true
}
],
"persistent_routes": [
{
"network_address": "10.0.1.0",
"netmask": "255.255.255.0",
"gateway_address": "192.168.22.1",
"metric": 1,
"metric_set_to_default": false
},
{
"network_address": "10.0.3.0",
"netmask": "255.255.255.0",
"gateway_address": "192.168.22.1",
"metric": 1,
"metric_set_to_default": false
},
...
]
},
"ipv6_route_table": {
"active_routes": [
{
"interface": 1,
"metric": 331,
"network_destination": "::1/128",
"gateway": "On-link",
"metric_set_to_default": false
},
{
"interface": 12,
"metric": 271,
"network_destination": "2001:db8::/64",
"gateway": "fe80::1",
"metric_set_to_default": false
},
...
{
"interface": 12,
"metric": 271,
"network_destination": "ff00::/8",
"gateway": "On-link",
"metric_set_to_default": false
}
],
"persistent_routes": [
{
"interface": 0,
"metric": 4294967295,
"network_destination": "2001:db8::/64",
"gateway": "fe80::1",
"metric_set_to_default": false
}
]
}
}
"""
import re
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`route print` command parser'
author = 'joehacksalot'
author_email = 'joehacksalot@gmail.com'
details = 'See also: `route` command parser'
compatible = ['win32']
magic_commands = ['route print']
tags = ['command']
__version__ = info.version
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Parsed dictionary. The raw and processed data structures are the same.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output = {}
if jc.utils.has_data(data):
raw_data = {
"interface_list": [],
"ipv4_route_table": {
"active_routes": [],
"persistent_routes": []
},
"ipv6_route_table": {
"active_routes": [],
"persistent_routes": []
}
}
lines = data.splitlines()
_parse_interface_list(raw_data, _PushbackIterator(iter(lines)))
_parse_ipv4_route_table(raw_data, _PushbackIterator(iter(lines)))
_parse_ipv6_route_table(raw_data, _PushbackIterator(iter(lines)))
raw_output = raw_data
return raw_output if raw else _process(raw_output)
def _process(proc_data):
"""
Final processing to conform to the schema.
Parameters:
proc_data: (Dictionary) raw structured data to process
Returns:
Processed Dictionary. Structured data to conform to the schema.
"""
if not proc_data:
return {}
for interface in proc_data['interface_list']:
if interface["mac_address"] == '' or interface["mac_address"] == '00 00 00 00 00 00 00 e0': # Placeholder MAC address for virtual adapters
mac_address = None
else:
mac_address = interface["mac_address"].replace(" ", ":")
interface["mac_address"] = mac_address
interface["interface_index"] = jc.utils.convert_to_int(interface["interface_index"])
for ipv4_active_route in proc_data['ipv4_route_table']['active_routes']:
if ipv4_active_route["metric"] != "Default":
ipv4_active_route["metric"] = jc.utils.convert_to_int(ipv4_active_route["metric"])
ipv4_active_route["metric_set_to_default"] = False
else:
ipv4_active_route["metric"] = None
ipv4_active_route["metric_set_to_default"] = True
for ipv4_persistent_route in proc_data['ipv4_route_table']['persistent_routes']:
if ipv4_persistent_route["metric"] != "Default":
ipv4_persistent_route["metric"] = jc.utils.convert_to_int(ipv4_persistent_route["metric"])
ipv4_persistent_route["metric_set_to_default"] = False
else:
ipv4_persistent_route["metric"] = None
ipv4_persistent_route["metric_set_to_default"] = True
for ipv6_active_route in proc_data['ipv6_route_table']['active_routes']:
ipv6_active_route["interface"] = jc.utils.convert_to_int(ipv6_active_route["interface"])
if ipv6_active_route["metric"] != "Default":
ipv6_active_route["metric"] = jc.utils.convert_to_int(ipv6_active_route["metric"])
ipv6_active_route["metric_set_to_default"] = False
else:
ipv6_active_route["metric"] = None
ipv6_active_route["metric_set_to_default"] = True
for ipv6_persistent_route in proc_data['ipv6_route_table']['persistent_routes']:
ipv6_persistent_route["interface"] = jc.utils.convert_to_int(ipv6_persistent_route["interface"])
if ipv6_persistent_route["metric"] != "Default":
ipv6_persistent_route["metric"] = jc.utils.convert_to_int(ipv6_persistent_route["metric"])
ipv6_persistent_route["metric_set_to_default"] = False
else:
ipv6_persistent_route["metric"] = None
ipv6_persistent_route["metric_set_to_default"] = True
return proc_data
class _PushbackIterator:
"""Iterator that allows pushing back values onto the iterator. Supports handing off
parsing to localized parsers while maintaining line synchonization."""
def __init__(self, iterator):
self.iterator = iterator
self.pushback_stack = []
def __iter__(self):
return self
def __next__(self):
if self.pushback_stack:
return self.pushback_stack.pop()
else:
return next(self.iterator)
def pushback(self, value):
self.pushback_stack.append(value)
def contains(self, pattern):
iter_lines = list(self.iterator)
list_lines = self.pushback_stack.copy()
list_lines.extend(iter_lines)
self.iterator = iter(list_lines)
self.pushback_stack = []
# Check the pushback stack first
for line in list_lines:
if re.match(pattern, line):
return True
return False
def skip_until(self, pattern):
for line in self:
if re.match(pattern, line):
return line
return None
def _parse_interface_list(data, lines_iter):
start_of_interface_list_pattern = r'^Interface List'
if lines_iter.contains(start_of_interface_list_pattern):
line = lines_iter.skip_until(start_of_interface_list_pattern)
for line in lines_iter:
if re.match(r'^=+$', line):
break # End of interface list
interface_index = line[:5].replace(".", "").strip()
mac_address = line[5:30].replace(".","").strip()
description = line[30:].strip()
data['interface_list'].append({
"interface_index": interface_index,
"mac_address": mac_address,
"description": description
})
def _parse_ipv4_route_table(data, lines_iter):
def _parse_ipv4_active_routes(data, lines_iter):
line = lines_iter.skip_until(r'^Active Routes')
line = next(lines_iter, '') # Skip the header line
if line.strip() == 'None':
return
for line in lines_iter:
if re.match(r'^=+$', line):
break # End of interface list
if 'Default Gateway' in line:
continue
lines_split = line.split()
network_destination = lines_split[0]
netmask = lines_split[1]
gateway = lines_split[2]
interface = lines_split[3]
metric = lines_split[4]
data['ipv4_route_table']["active_routes"].append({
"network_destination": network_destination,
"netmask": netmask,
"gateway": gateway,
"interface": interface,
"metric": metric
})
def _parse_ipv4_persistent_routes(data, lines_iter):
line = lines_iter.skip_until(r'^Persistent Routes')
line = next(lines_iter, '') # line is either "None" and we abort parsing this section or we skip header line
if line.strip() == 'None':
return
for line in lines_iter:
if re.match(r'^=+$', line):
break
lines_split = line.split()
network_address = lines_split[0]
netmask = lines_split[1]
gateway_address = lines_split[2]
metric = lines_split[3]
data['ipv4_route_table']["persistent_routes"].append({
"network_address": network_address,
"netmask": netmask,
"gateway_address": gateway_address,
"metric": metric
})
start_of_ipv4_route_table_pattern = r'^IPv4 Route Table'
if lines_iter.contains(start_of_ipv4_route_table_pattern):
line = lines_iter.skip_until(start_of_ipv4_route_table_pattern)
line = next(lines_iter, '') # Skip the separator line
_parse_ipv4_active_routes(data, lines_iter)
_parse_ipv4_persistent_routes(data, lines_iter)
def _parse_ipv6_route_table(data, lines_iter):
def _parse_ipv6_active_routes(data, lines_iter):
line = lines_iter.skip_until(r'^Active Routes')
line = next(lines_iter, '') # line is either "None" and we abort parsing this section or we skip header line
if line.strip() == 'None':
return
for line in lines_iter:
if re.match(r'^=+$', line):
break
split_line = line.split()
interface = split_line[0]
metric = split_line[1]
network_destination = split_line[2]
if len(split_line) > 3:
gateway = split_line[3]
else:
gateway = next(lines_iter, '').strip()
data['ipv6_route_table']["active_routes"].append({
"interface": interface,
"metric": metric,
"network_destination": network_destination,
"gateway": gateway
})
def _parse_ipv6_persistent_routes(data, lines_iter):
line = lines_iter.skip_until(r'^Persistent Routes')
line = next(lines_iter, '') # line is either "None" and we abort parsing this section or we skip header line
if line.strip() == 'None':
return
for line in lines_iter:
if re.match(r'^=+$', line):
break
split_line = line.split()
interface = split_line[0]
metric = split_line[1]
network_destination = split_line[2]
if len(split_line) > 3:
gateway = split_line[3]
else:
gateway = next(lines_iter, '').strip()
data['ipv6_route_table']["persistent_routes"].append({
"interface": interface,
"metric": metric,
"network_destination": network_destination,
"gateway": gateway
})
start_of_ipv6_route_table_pattern = r'^IPv6 Route Table'
if lines_iter.contains(start_of_ipv6_route_table_pattern):
line = lines_iter.skip_until(start_of_ipv6_route_table_pattern)
line = next(lines_iter, '') # Skip the separator line
_parse_ipv6_active_routes(data, lines_iter)
_parse_ipv6_persistent_routes(data, lines_iter)

View File

@@ -185,7 +185,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.8'
version = '1.9'
description = '`rpm -qi` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -256,8 +256,6 @@ def parse(data, raw=False, quiet=False):
raw_output = []
entry_obj = {}
last_entry = None
this_entry = None
desc_entry = False
desc_en_entry = False
description = []
@@ -268,17 +266,13 @@ def parse(data, raw=False, quiet=False):
split_line = line.split(': ', maxsplit=1)
if (split_line[0].startswith('Name') or split_line[0] == 'Package') and len(split_line) == 2:
this_entry = split_line[1].strip()
if this_entry != last_entry:
if entry_obj:
if description:
entry_obj['description'] = ' '.join(description)
raw_output.append(entry_obj)
entry_obj = {}
last_entry = this_entry
desc_entry = False
desc_en_entry = False
if entry_obj:
if description:
entry_obj['description'] = ' '.join(description)
raw_output.append(entry_obj)
entry_obj = {}
desc_entry = False
desc_en_entry = False
if line.startswith('Description :'):
desc_entry = True

View File

@@ -132,7 +132,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.4'
version = '1.5'
description = '`/usr/bin/time` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -171,13 +171,18 @@ def _process(proc_data):
proc_data['command_being_timed'] = proc_data['command_being_timed'][1:-1]
if 'elapsed_time' in proc_data:
proc_data['elapsed_time'] = proc_data['elapsed_time'].replace('.', ':')
*hours, minutes, seconds, centiseconds = proc_data['elapsed_time'].split(':')
proc_data['elapsed_time'] = proc_data['elapsed_time'][::-1].replace(':', '.', 1)[::-1]
*hours, minutes, ss = proc_data['elapsed_time'].split(':')
if '.' in ss:
seconds, centiseconds = ss.split('.')
else:
seconds = ss
centiseconds = '0'
if hours:
proc_data['elapsed_time_hours'] = jc.utils.convert_to_int(hours[0])
else:
proc_data['elapsed_time_hours'] = 0
proc_data['elapsed_time_minutes'] = jc.utils.convert_to_int(minutes)
proc_data['elapsed_time_seconds'] = jc.utils.convert_to_int(seconds)
proc_data['elapsed_time_centiseconds'] = jc.utils.convert_to_int(centiseconds)

View File

@@ -45,23 +45,31 @@ All `-` values are converted to `null`
"cpu_hardware": float,
"cpu_software": float,
"cpu_steal": float,
"mem_total": float, # [0]
"mem_free": float, # [0]
"mem_used": float, # [0]
"mem_buff_cache": float, # [0]
"swap_total": float, # [0]
"swap_free": float, # [0]
"swap_used": float, # [0]
"mem_available": float, # [0]
"mem_unit": string,
"mem_total": float,
"mem_free": float,
"mem_used": float,
"mem_buff_cache": float,
"swap_unit": string,
"swap_total": float,
"swap_free": float,
"swap_used": float,
"mem_available": float,
"processes": [
{
"pid": integer,
"user": string,
"priority": integer,
"nice": integer,
"virtual_mem": float, # [1]
"resident_mem": float, # [1]
"shared_mem": float, # [1]
"virtual_mem": float,
"virtual_mem_bytes": integer,
"virtual_mem_unit": string,
"resident_mem": float,
"resident_mem_bytes": integer,
"resident_mem_unit": string,
"shared_mem": float,
"shared_mem_bytes": integer,
"shared_mem_unit": string,
"status": string,
"percent_cpu": float,
"percent_mem": float,
@@ -82,9 +90,15 @@ All `-` values are converted to `null`
"thread_count": integer,
"last_used_processor": integer,
"time": string,
"swap": float, # [1]
"code": float, # [1]
"data": float, # [1]
"swap": float,
"swap_bytes": integer,
"swap_unit": string,
"code": float,
"code_bytes": integer,
"code_unit": string,
"data": float,
"data_bytes": integer,
"data_unit": string,
"major_page_fault_count": integer,
"minor_page_fault_count": integer,
"dirty_pages_count": integer,
@@ -103,7 +117,9 @@ All `-` values are converted to `null`
]
"major_page_fault_count_delta": integer,
"minor_page_fault_count_delta": integer,
"used": float, # [1]
"used": float,
"used_bytes": integer,
"used_unit": string,
"ipc_namespace_inode": integer,
"mount_namespace_inode": integer,
"net_namespace_inode": integer,
@@ -124,9 +140,6 @@ All `-` values are converted to `null`
}
]
[0] Values are in the units output by `top`
[1] Unit suffix stripped during float conversion
Examples:
$ top -b -n 3 | jc --top -p
@@ -316,7 +329,7 @@ from jc.parsers.universal import sparse_table_parse as parse_table
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.2'
version = '1.3'
description = '`top -b` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -440,13 +453,25 @@ def _process(proc_data: List[Dict], quiet=False) -> List[Dict]:
'mem_available', 'virtual_mem', 'resident_mem', 'shared_mem', 'swap', 'code', 'data', 'used'
}
bytes_list: Set = {
'mem_total', 'mem_free', 'mem_used', 'mem_available', 'mem_buff_cache',
'swap_total', 'swap_free', 'swap_used', 'virtual_mem', 'resident_mem',
'shared_mem', 'swap', 'code', 'data', 'used'
}
for idx, item in enumerate(proc_data):
for key in item:
for key in item.copy():
# root truncation warnings
if isinstance(item[key], str) and item[key].endswith('+') and not quiet:
jc.utils.warning_message([f'item[{idx}]["{key}"] was truncated by top'])
# root int and float conversions
if key in bytes_list:
if key.startswith('mem_'):
item[key + '_bytes'] = jc.utils.convert_size_to_int(item[key] + item['mem_unit'])
if key.startswith('swap_'):
item[key + '_bytes'] = jc.utils.convert_size_to_int(item[key] + item['swap_unit'])
if key in int_list:
item[key] = jc.utils.convert_to_int(item[key])
@@ -463,7 +488,8 @@ def _process(proc_data: List[Dict], quiet=False) -> List[Dict]:
jc.utils.warning_message([f'Unknown field detected at item[{idx}]["processes"]: {old_key}'])
# cleanup values
for key in proc.keys():
proc_copy = proc.copy()
for key in proc_copy.keys():
# set dashes to nulls
if proc[key] == '-':
@@ -482,6 +508,13 @@ def _process(proc_data: List[Dict], quiet=False) -> List[Dict]:
# do int/float conversions for the process objects
if proc[key]:
if key in bytes_list:
if proc[key][-1] not in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'):
proc[key + '_unit'] = proc[key][-1]
else:
proc[key + '_unit'] = 'b'
proc[key + '_bytes'] = jc.utils.convert_size_to_int(proc[key], posix_mode=True)
if key in int_list:
proc[key] = jc.utils.convert_to_int(proc[key])
@@ -604,6 +637,7 @@ def parse(
line_list = line.split()
item_obj.update(
{
'mem_unit': line_list[0],
'mem_total': line_list[3],
'mem_free': line_list[5],
'mem_used': line_list[7],
@@ -617,6 +651,7 @@ def parse(
line_list = line.split()
item_obj.update(
{
'swap_unit': line_list[0],
'swap_total': line_list[2],
'swap_free': line_list[4],
'swap_used': line_list[6],

View File

@@ -43,23 +43,39 @@ Schema:
"cpu_hardware": float,
"cpu_software": float,
"cpu_steal": float,
"mem_total": float, # [0]
"mem_free": float, # [0]
"mem_used": float, # [0]
"mem_buff_cache": float, # [0]
"swap_total": float, # [0]
"swap_free": float, # [0]
"swap_used": float, # [0]
"mem_available": float, # [0]
"mem_unit": string,
"swap_unit": string,
"mem_total": float,
"mem_total_bytes": integer,
"mem_free": float,
"mem_free_bytes": integer,
"mem_used": float,
"mem_used_bytes": integer,
"mem_buff_cache": float,
"mem_buff_cache_bytes": integer,
"swap_total": float,
"swap_total_bytes": integer,
"swap_free": float,
"swap_free_bytes": integer,
"swap_used": float,
"swap_used_bytes": integer,
"mem_available": float,
"mem_available_bytes": integer,
"processes": [
{
"pid": integer,
"user": string,
"priority": integer,
"nice": integer,
"virtual_mem": float, # [1]
"resident_mem": float, # [1]
"shared_mem": float, # [1]
"virtual_mem": float,
"virtual_mem_unit": string,
"virtual_mem_bytes": integer,
"resident_mem": float,
"resident_mem_unit": string,
"resident_mem_bytes": integer,
"shared_mem": float,
"shared_mem_unit": string,
"shared_mem_bytes": integer,
"status": string,
"percent_cpu": float,
"percent_mem": float,
@@ -80,9 +96,15 @@ Schema:
"thread_count": integer,
"last_used_processor": integer,
"time": string,
"swap": float, # [1]
"code": float, # [1]
"data": float, # [1]
"swap": float,
"swap_unit": string,
"swap_bytes": integer,
"code": float,
"code_unit": string,
"code_bytes": integer
"data": float,
"data_unit": string,
"data_bytes": integer,
"major_page_fault_count": integer,
"minor_page_fault_count": integer,
"dirty_pages_count": integer,
@@ -101,7 +123,9 @@ Schema:
]
"major_page_fault_count_delta": integer,
"minor_page_fault_count_delta": integer,
"used": float, # [1]
"used": float,
"used_unit": string,
"used_bytes": integer,
"ipc_namespace_inode": integer,
"mount_namespace_inode": integer,
"net_namespace_inode": integer,
@@ -128,9 +152,6 @@ Schema:
}
}
[0] Values are in the units output by `top`
[1] Unit suffix stripped during float conversion
Examples:
$ top -b | jc --top-s
@@ -153,7 +174,7 @@ from jc.parsers.universal import sparse_table_parse as parse_table
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.2'
version = '1.3'
description = '`top -b` command streaming parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -277,12 +298,24 @@ def _process(proc_data: Dict, idx=0, quiet=False) -> Dict:
'mem_available', 'virtual_mem', 'resident_mem', 'shared_mem', 'swap', 'code', 'data', 'used'
}
for key in proc_data:
bytes_list: Set = {
'mem_total', 'mem_free', 'mem_used', 'mem_available', 'mem_buff_cache',
'swap_total', 'swap_free', 'swap_used', 'virtual_mem', 'resident_mem',
'shared_mem', 'swap', 'code', 'data', 'used'
}
for key in proc_data.copy():
# root truncation warnings
if isinstance(proc_data[key], str) and proc_data[key].endswith('+') and not quiet:
jc.utils.warning_message([f'item[{idx}]["{key}"] was truncated by top'])
# root int and float conversions
if key in bytes_list:
if key.startswith('mem_'):
proc_data[key + '_bytes'] = jc.utils.convert_size_to_int(proc_data[key] + proc_data['mem_unit'])
if key.startswith('swap_'):
proc_data[key + '_bytes'] = jc.utils.convert_size_to_int(proc_data[key] + proc_data['swap_unit'])
if key in int_list:
proc_data[key] = jc.utils.convert_to_int(proc_data[key])
@@ -299,7 +332,8 @@ def _process(proc_data: Dict, idx=0, quiet=False) -> Dict:
jc.utils.warning_message([f'Unknown field detected at item[{idx}]["processes"]: {old_key}'])
# cleanup values
for key in proc.keys():
proc_copy = proc.copy()
for key in proc_copy.keys():
# set dashes to nulls
if proc[key] == '-':
@@ -318,6 +352,13 @@ def _process(proc_data: Dict, idx=0, quiet=False) -> Dict:
# do int/float conversions for the process objects
if proc[key]:
if key in bytes_list:
if proc[key][-1] not in ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'):
proc[key + '_unit'] = proc[key][-1]
else:
proc[key + '_unit'] = 'b'
proc[key + '_bytes'] = jc.utils.convert_size_to_int(proc[key], posix_mode=True)
if key in int_list:
proc[key] = jc.utils.convert_to_int(proc[key])
@@ -448,6 +489,7 @@ def parse(
line_list = line.split()
output_line.update(
{
'mem_unit': line_list[0],
'mem_total': line_list[3],
'mem_free': line_list[5],
'mem_used': line_list[7],
@@ -461,6 +503,7 @@ def parse(
line_list = line.split()
output_line.update(
{
'swap_unit': line_list[0],
'swap_total': line_list[2],
'swap_free': line_list[4],
'swap_used': line_list[6],
@@ -489,3 +532,5 @@ def parse(
if process_list:
output_line['processes'] = parse_table(process_list)
yield output_line if raw else _process(output_line, idx=idx, quiet=quiet)
return None

View File

@@ -27,6 +27,8 @@ Schema:
{
"destination_ip": string,
"destination_name": string,
"max_hops": integer,
"data_bytes": integer,
"hops": [
{
"hop": integer,
@@ -49,6 +51,8 @@ Examples:
{
"destination_ip": "216.58.194.46",
"destination_name": "google.com",
"max_hops": 64,
"data_bytes": 50,
"hops": [
{
"hop": 1,
@@ -84,6 +88,8 @@ Examples:
{
"destination_ip": "216.58.194.46",
"destination_name": "google.com",
"max_hops": "64",
"data_bytes": "50",
"hops": [
{
"hop": "1",
@@ -119,11 +125,12 @@ import re
from decimal import Decimal
import jc.utils
from copy import deepcopy
from jc.exceptions import ParseError
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.7'
version = '1.9'
description = '`traceroute` and `traceroute6` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -164,10 +171,11 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''
RE_HEADER = re.compile(r'(\S+)\s+\((\d+\.\d+\.\d+\.\d+|[0-9a-fA-F:]+)\)')
RE_HEADER = re.compile(r'traceroute6? to (\S+)\s+\((\d+\.\d+\.\d+\.\d+|[0-9a-fA-F:]+)\)')
RE_HEADER_HOPS_BYTES = re.compile(r'(\d+) hops max, (\d+) byte packets')
RE_PROBE_NAME_IP = re.compile(r'(\S+)\s+\((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[0-9a-fA-F:]+)\)+')
RE_PROBE_IP_ONLY = re.compile(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+([^\(])')
RE_PROBE_IPV6_ONLY = re.compile(r'(([a-f0-9:]+:+)+[a-f0-9]+)')
RE_PROBE_IPV6_ONLY = re.compile(r'(([a-f0-9]*:)+[a-f0-9]+)')
RE_PROBE_BSD_IPV6 = re.compile(r'\b(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}\b')
RE_HOP = re.compile(r'^\s*(\d+)?\s+(.+)$')
RE_PROBE_ASN = re.compile(r'\[AS(\d+)\]')
@@ -175,9 +183,11 @@ RE_PROBE_RTT_ANNOTATION = re.compile(r'(?:(\d+(?:\.?\d+)?)\s+ms|(\s+\*\s+))\s*(!
class _Traceroute(object):
def __init__(self, dest_name, dest_ip):
def __init__(self, dest_name, dest_ip, max_hops=None, data_bytes=None):
self.dest_name = dest_name
self.dest_ip = dest_ip
self.max_hops = max_hops
self.data_bytes = data_bytes
self.hops = []
def add_hop(self, hop):
@@ -291,8 +301,23 @@ def _get_probes(hop_string: str):
return probes
def _loads(data):
lines = data.splitlines()
def _loads(data: str, quiet: bool):
lines = []
# remove any warning lines
for data_line in data.splitlines():
if 'traceroute: Warning: ' not in data_line and 'traceroute6: Warning: ' not in data_line:
lines.append(data_line)
else:
continue
# check if header row exists, otherwise add a dummy header
if not lines[0].startswith('traceroute to ') and not lines[0].startswith('traceroute6 to '):
lines[:0] = ['traceroute to <<_>> (<<_>>), ? hops max, ? byte packets']
# print warning to STDERR
if not quiet:
jc.utils.warning_message(['No header row found. For destination info redirect STDERR to STDOUT'])
# Get headers
match_dest = RE_HEADER.search(lines[0])
@@ -301,8 +326,15 @@ def _loads(data):
dest_name = match_dest.group(1)
dest_ip = match_dest.group(2)
m = RE_HEADER_HOPS_BYTES.search(lines[0])
max_hops = None
data_bytes = None
if m:
max_hops = m.group(1)
data_bytes = m.group(2)
# The Traceroute node is the root of the tree
traceroute = _Traceroute(dest_name, dest_ip)
traceroute = _Traceroute(dest_name, dest_ip, max_hops, data_bytes)
# Parse the remaining lines, they should be only hops/probes
for line in lines[1:]:
@@ -312,16 +344,17 @@ def _loads(data):
hop_match = RE_HOP.match(line)
if hop_match.group(1):
hop_index = int(hop_match.group(1))
else:
hop_index = None
if hop_match:
if hop_match.group(1):
hop_index = int(hop_match.group(1))
else:
hop_index = None
if hop_index is not None:
hop = _Hop(hop_index)
traceroute.add_hop(hop)
if hop_index is not None:
hop = _Hop(hop_index)
traceroute.add_hop(hop)
hop_string = hop_match.group(2)
hop_string = hop_match.group(2)
probes = _get_probes(hop_string)
for probe in probes:
@@ -330,11 +363,25 @@ def _loads(data):
return traceroute
class ParseError(Exception):
pass
def _serialize_hop(hop: _Hop):
hop_obj = {}
hop_obj['hop'] = str(hop.idx)
probe_list = []
if hop.probes:
for probe in hop.probes:
probe_obj = {
'annotation': probe.annotation,
'asn': None if probe.asn is None else str(probe.asn),
'ip': probe.ip,
'name': probe.name,
'rtt': None if probe.rtt is None else str(probe.rtt)
}
probe_list.append(probe_obj)
########################################################################################
hop_obj['probes'] = probe_list
return hop_obj
def _process(proc_data):
@@ -349,26 +396,20 @@ def _process(proc_data):
Dictionary. Structured to conform to the schema.
"""
int_list = {'hop', 'asn'}
int_list = {'hop', 'asn', 'max_hops', 'data_bytes'}
float_list = {'rtt'}
if 'hops' in proc_data:
for entry in proc_data['hops']:
for key in entry:
if key in int_list:
entry[key] = jc.utils.convert_to_int(entry[key])
for entry in proc_data.get('hops', []):
_process(entry)
for entry in proc_data.get('probes', []):
_process(entry)
if key in float_list:
entry[key] = jc.utils.convert_to_float(entry[key])
for key in proc_data:
if key in int_list:
proc_data[key] = jc.utils.convert_to_int(proc_data[key])
if 'probes' in entry:
for item in entry['probes']:
for key in item:
if key in int_list:
item[key] = jc.utils.convert_to_int(item[key])
if key in float_list:
item[key] = jc.utils.convert_to_float(item[key])
if key in float_list:
proc_data[key] = jc.utils.convert_to_float(proc_data[key])
return proc_data
@@ -393,52 +434,17 @@ def parse(data, raw=False, quiet=False):
raw_output = {}
if jc.utils.has_data(data):
# remove any warning lines
new_data = []
for data_line in data.splitlines():
if 'traceroute: Warning: ' not in data_line and 'traceroute6: Warning: ' not in data_line:
new_data.append(data_line)
else:
continue
# check if header row exists, otherwise add a dummy header
if not new_data[0].startswith('traceroute to ') and not new_data[0].startswith('traceroute6 to '):
new_data[:0] = ['traceroute to <<_>> (<<_>>), 30 hops max, 60 byte packets']
# print warning to STDERR
if not quiet:
jc.utils.warning_message(['No header row found. For destination info redirect STDERR to STDOUT'])
data = '\n'.join(new_data)
tr = _loads(data)
hops = tr.hops
tr = _loads(data, quiet)
hops_list = []
if hops:
for hop in hops:
hop_obj = {}
hop_obj['hop'] = str(hop.idx)
probe_list = []
if hop.probes:
for probe in hop.probes:
probe_obj = {
'annotation': probe.annotation,
'asn': None if probe.asn is None else str(probe.asn),
'ip': probe.ip,
'name': probe.name,
'rtt': None if probe.rtt is None else str(probe.rtt)
}
probe_list.append(probe_obj)
hop_obj['probes'] = probe_list
hops_list.append(hop_obj)
for hop in tr.hops:
hops_list.append(_serialize_hop(hop))
raw_output = {
'destination_ip': tr.dest_ip,
'destination_name': tr.dest_name,
'max_hops': tr.max_hops,
'data_bytes': tr.data_bytes,
'hops': hops_list
}

277
jc/parsers/traceroute_s.py Normal file
View File

@@ -0,0 +1,277 @@
r"""jc - JSON Convert `traceroute` command output streaming parser
> This streaming parser outputs JSON Lines (cli) or returns an Iterable of
> Dictionaries (module)
Supports `traceroute` and `traceroute6` output.
> Note: On some operating systems you will need to redirect `STDERR` to
> `STDOUT` for destination info since the header line is sent to
> `STDERR`. A warning message will be printed to `STDERR` if the
> header row is not found.
>
> e.g. `$ traceroute 8.8.8.8 2>&1 | jc --traceroute-s`
Usage (cli):
$ traceroute 1.2.3.4 | jc --traceroute-s
Usage (module):
import jc
result = jc.parse('traceroute_s', traceroute_command_output.splitlines())
for item in result:
# do something
Schema:
{
# 'header' or 'hop'
"type": string,
# 'header' type has the fields below:
"destination_ip": string,
"destination_name": string,
"max_hops": integer,
"data_bytes": integer,
# 'hop' type has the fields below:
"hop": integer,
"probes": [
{
"annotation": string,
"asn": integer,
"ip": string,
"name": string,
"rtt": float
}
]
# below object only exists if using -qq or ignore_exceptions=True
"_jc_meta": {
"success": boolean, # false if error parsing
"error": string, # exists if "success" is false
"line": string # exists if "success" is false
}
}
Examples:
$ traceroute google.com | jc --traceroute-s -p
{
"type": "header",
"destination_ip": "216.58.194.46",
"destination_name": "google.com",
"max_hops": 30,
"data_bytes": 60
}
{
"type": "hop",
"hop": 1,
"probes": [
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": 198.574
},
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": null
},
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": 198.65
}
]
}
...
$ traceroute google.com | jc --traceroute-s -p -r
{
"type": "header",
"destination_ip": "216.58.194.46",
"destination_name": "google.com",
"max_hops": "30",
"data_bytes": "60"
}
{
"type": "hop",
"hop": "1",
"probes": [
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": "198.574"
},
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": null
},
{
"annotation": null,
"asn": null,
"ip": "216.230.231.141",
"name": "216-230-231-141.static.houston.tx.oplink.net",
"rtt": "198.650"
}
]
}
...
"""
from typing import Optional
import jc.utils
from jc.exceptions import ParseError
from jc.streaming import (
add_jc_meta, streaming_input_type_check, streaming_line_input_type_check, raise_or_yield
)
from .traceroute import RE_HEADER, RE_HOP, RE_HEADER_HOPS_BYTES, _Hop, _loads, _process, _serialize_hop
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`traceroute` and `traceroute6` command streaming parser'
author = 'Shintaro Kojima'
author_email = 'goodies@codeout.net'
compatible = ['linux', 'darwin', 'freebsd']
tags = ['command']
streaming = True
__version__ = info.version
'''
Copyright (C) 2015 Luis Benitez
Parses the output of a traceroute execution into an AST (Abstract Syntax Tree).
The MIT License (MIT)
Copyright (c) 2014 Luis Benitez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''
def _hop_output(hop: _Hop, raw: bool):
raw_output = {
'type': 'hop',
**_serialize_hop(hop),
}
return raw_output if raw else _process(raw_output)
@add_jc_meta
def parse(data, raw=False, quiet=False, ignore_exceptions=False):
"""
Main text parsing function. Returns an iterable object.
Parameters:
data: (iterable) line-based text data to parse
(e.g. sys.stdin or str.splitlines())
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
ignore_exceptions: (boolean) ignore parsing exceptions if True
Returns:
Iterable of Dictionaries
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
streaming_input_type_check(data)
# Estimated number of probe packets per hop. See `traceroute -q` on Linux, for example.
queries = 0
# Accumulated hop across multiple lines
hop_cache: Optional[_Hop] = None
for line in data: # type: str
try:
streaming_line_input_type_check(line)
if RE_HEADER.search(line):
tr = _loads(line, quiet)
raw_output = {
'type': 'header',
'destination_ip': tr.dest_ip,
'destination_name': tr.dest_name,
'max_hops': tr.max_hops,
'data_bytes': tr.data_bytes
}
yield raw_output if raw else _process(raw_output)
else:
m = RE_HOP.match(line)
if not m:
continue
# A single hop can wrap across multiple lines, e.g.:
#
# 6 [AS0] 94.142.122.45 (94.142.122.45) 42.790 ms 46.352 ms
# [AS0] 94.142.122.44 (94.142.122.44) 41.479 ms
#
if not m.group(1):
if not hop_cache:
raise ParseError('No hop index found')
# If the hop index is not found, prepend the hop index (6) to the following lines before parsing.
line = f"{hop_cache.idx} {line}"
# Specify quiet=True to suppress the 'No header row found' warning for hop lines
tr = _loads(line, quiet=True)
if not tr.hops:
continue
hop_cache.probes.extend(tr.hops[0].probes)
else:
# if the hop index is found, yield the previous hop
if hop_cache:
yield _hop_output(hop_cache, raw)
hop_cache = None
# Specify quiet=True to suppress the 'No header row found' warning for hop lines
tr = _loads(line, quiet=True)
if not tr.hops:
continue
hop_cache = tr.hops[0]
except Exception as e:
yield raise_or_yield(ignore_exceptions, e, line)
if hop_cache:
yield _hop_output(hop_cache, raw)

View File

@@ -65,7 +65,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.9'
version = '1.10'
description = '`uptime` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -162,7 +162,7 @@ def parse(data, raw=False, quiet=False):
raw_output = {}
if jc.utils.has_data(data):
if 'users' in data:
if 'user' in data:
# standard uptime output
time, _, *uptime, users, _, _, _, load_1m, load_5m, load_15m = data.split()

273
jc/parsers/wg_show.py Normal file
View File

@@ -0,0 +1,273 @@
r"""jc - JSON Convert `wg show` command output parser
Parses the output of the `wg show all dump` command, providing structured JSON output for easy integration and analysis.
Usage (cli):
$ wg show all dump | jc --wg-show
or
$ jc wg show all dump
Usage (module):
import jc
result = jc.parse('wg-show', wg_command_output)
Schema:
[
{
"device": string,
"private_key": string,
"public_key": string,
"listen_port": integer,
"fwmark": integer,
"peers": [
{
"public_key": string,
"preshared_key": string,
"endpoint": string,
"latest_handshake": integer,
"transfer_rx": integer,
"transfer_sx": integer,
"persistent_keepalive": integer,
"allowed_ips": [
string
]
}
]
}
]
Examples:
$ wg show all dump | jc --wg-show -p
[
{
"device": "wg0",
"private_key": "aEbVdvHSEp3oofHDNVCsUoaRSxk1Og8/pTLof5yF+1M=",
"public_key": "OIxbQszw1chdO5uigAxpsl4fc/h04yMYafl72gUbakM=",
"listen_port": 51820,
"fwmark": null,
"peers": [
{
"public_key": "sQFGAhSdx0aC7DmTFojzBOW8Ccjv1XV5+N9FnkZu5zc=",
"preshared_key": null,
"endpoint": "79.134.136.199:40036",
"latest_handshake": 1728809756,
"transfer_rx": 1378724,
"transfer_sx": 406524,
"persistent_keepalive": null,
"allowed_ips": ["10.10.0.2/32"]
},
{
"public_key": "B9csmpvrv4Q7gpjc6zAbNNO8hIOYfpBqxmik2aNpwwE=",
"preshared_key": null,
"endpoint": "79.134.136.199:35946",
"latest_handshake": 1728809756,
"transfer_rx": 4884248,
"transfer_sx": 3544596,
"persistent_keepalive": null,
"allowed_ips": ["10.10.0.3/32"]
},
{
"public_key": "miiSYR5UdevREhlWpmnci+vv/dEGLHbNtKu7u1CuOD4=",
"preshared_key": null,
"allowed_ips": ["10.10.0.4/32"]
},
{
"public_key": "gx9+JHLHJvOfBNjTmZ8KQAnThFFiZMQrX1kRaYcIYzw=",
"preshared_key": null,
"endpoint": "173.244.225.194:45014",
"latest_handshake": 1728809827,
"transfer_rx": 1363652,
"transfer_sx": 458252,
"persistent_keepalive": null,
"allowed_ips": ["10.10.0.5/32"]
}
]
}
]
$ wg show all dump | jc --wg-show -p -r
[
{
"device": "wg0",
"private_key": "aEbVdvHSEp3oofHDNVCsUoaRSxk1Og8/pTLof5yF+1M=",
"public_key": "OIxbQszw1chdO5uigAxpsl4fc/h04yMYafl72gUbakM=",
"listen_port": 51820,
"fwmark": null,
"peers": {
"sQFGAhSdx0aC7DmTFojzBOW8Ccjv1XV5+N9FnkZu5zc=": {
"preshared_key": null,
"endpoint": "79.134.136.199:40036",
"latest_handshake": 1728809756,
"transfer_rx": 1378724,
"transfer_sx": 406524,
"persistent_keepalive": -1,
"allowed_ips": ["10.10.0.2/32"]
},
"B9csmpvrv4Q7gpjc6zAbNNO8hIOYfpBqxmik2aNpwwE=": {
"preshared_key": null,
"endpoint": "79.134.136.199:35946",
"latest_handshake": 1728809756,
"transfer_rx": 4884248,
"transfer_sx": 3544596,
"persistent_keepalive": -1,
"allowed_ips": ["10.10.0.3/32"]
},
"miiSYR5UdevREhlWpmnci+vv/dEGLHbNtKu7u1CuOD4=": {
"preshared_key": null,
"allowed_ips": ["10.10.0.4/32"]
},
"gx9+JHLHJvOfBNjTmZ8KQAnThFFiZMQrX1kRaYcIYzw=": {
"preshared_key": null,
"endpoint": "173.244.225.194:45014",
"latest_handshake": 1728809827,
"transfer_rx": 1363652,
"transfer_sx": 458252,
"persistent_keepalive": -1,
"allowed_ips": ["10.10.0.5/32"]
}
}
}
]
"""
from typing import List, Dict, Optional, Union
from jc.jc_types import JSONDictType
import jc.utils
import re
PeerData = Dict[str, Union[Optional[str], Optional[int], List[str]]]
DeviceData = Dict[str, Union[Optional[str], Optional[int], Dict[str, PeerData]]]
class info:
"""Provides parser metadata (version, author, etc.)"""
version = "1.0"
description = "`wg show` command parser"
author = "Hamza Saht"
author_email = "hamzasaht01@gmail.com"
compatible = ["linux", "darwin", "cygwin", "win32", "aix", "freebsd"]
tags = ["command"]
magic_commands = ["wg show"]
__version__ = info.version
def _process(proc_data: List[DeviceData]) -> List[JSONDictType]:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List[Dict]) Raw structured data to process
Returns:
List[Dict]: Structured data that conforms to the schema
"""
processed_data: List[JSONDictType] = []
for device in proc_data:
processed_device = {
"device": device["device"],
"private_key": device.get("private_key"),
"public_key": device.get("public_key"),
"listen_port": device.get("listen_port"),
"fwmark": device.get("fwmark"),
"peers": [
{
"public_key": peer_key,
"preshared_key": peer_data.get("preshared_key"),
"endpoint": peer_data.get("endpoint"),
"latest_handshake": peer_data.get("latest_handshake", 0),
"transfer_rx": peer_data.get("transfer_rx", 0),
"transfer_sx": peer_data.get("transfer_sx", 0),
"persistent_keepalive": peer_data.get("persistent_keepalive", -1),
"allowed_ips": peer_data.get("allowed_ips", []),
}
for peer_key, peer_data in device.get("peers", {}).items()
],
}
processed_data.append(processed_device)
return processed_data
def parse(data: str, raw: bool = False, quiet: bool = False) -> List[JSONDictType]:
"""
Main text parsing function.
Parses the output of the `wg` command, specifically `wg show all dump`, into structured JSON format.
Parameters:
data: (str) Text data to parse, typically the output from `wg show all dump`
raw: (bool) If True, returns unprocessed output
quiet: (bool) Suppress warning messages if True
Returns:
List[Dict]: Parsed data in JSON-friendly format, either raw or processed.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List[DeviceData] = []
current_device: Optional[str] = None
device_data: DeviceData = {}
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
fields = re.split(r"\s+", line.strip())
if len(fields) == 5:
device, private_key, public_key, listen_port, fwmark = fields
if current_device:
raw_output.append({"device": current_device, **device_data})
current_device = device
device_data = {
"private_key": private_key if private_key != "(none)" else None,
"public_key": public_key if public_key != "(none)" else None,
"listen_port": int(listen_port) if listen_port != "0" else None,
"fwmark": int(fwmark) if fwmark != "off" else None,
"peers": {},
}
elif len(fields) == 9:
(
interface,
public_key,
preshared_key,
endpoint,
allowed_ips,
latest_handshake,
transfer_rx,
transfer_tx,
persistent_keepalive,
) = fields
peer_data: PeerData = {
"preshared_key": preshared_key
if preshared_key != "(none)"
else None,
"endpoint": endpoint if endpoint != "(none)" else None,
"latest_handshake": int(latest_handshake),
"transfer_rx": int(transfer_rx),
"transfer_sx": int(transfer_tx),
"persistent_keepalive": int(persistent_keepalive)
if persistent_keepalive != "off"
else -1,
"allowed_ips": allowed_ips.split(",")
if allowed_ips != "(none)"
else [],
}
device_data["peers"][public_key] = {
k: v for k, v in peer_data.items() if v is not None
}
if current_device:
raw_output.append({"device": current_device, **device_data})
return raw_output if raw else _process(raw_output)

View File

@@ -25,6 +25,7 @@ Schema:
"user": string,
"event": string,
"writeable_tty": string,
"process": string,
"tty": string,
"time": string,
"epoch": integer, # [0]
@@ -136,7 +137,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.8'
version = '1.9'
description = '`who` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -254,6 +255,12 @@ def parse(data, raw=False, quiet=False):
raw_output.append(output_line)
continue
# some output contains process name between the username and pts
# pull the process name for use later if we can find it
user_process = None
if re.match(r'^\S+\s+[^ +-]+\s+pts\/\d+\s', line):
user_process = linedata.pop(1)
# user logins
output_line['user'] = linedata.pop(0)
@@ -262,6 +269,9 @@ def parse(data, raw=False, quiet=False):
output_line['tty'] = linedata.pop(0)
if user_process:
output_line['process'] = user_process
# mac
if re.match(r'[JFMASOND][aepuco][nbrynlgptvc]', linedata[0]):
output_line['time'] = ' '.join([linedata.pop(0),

View File

@@ -413,7 +413,7 @@ from jc.parsers.asn1crypto import pem, x509, jc_global
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.3'
version = '1.4'
description = 'X.509 PEM and DER certificate file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'

333
jc/parsers/x509_crl.py Normal file
View File

@@ -0,0 +1,333 @@
r"""jc - JSON Convert X.509 Certificate Revocation List format file parser
This parser will convert DER and PEM encoded X.509 certificate revocation
list files.
Usage (cli):
$ cat certificateRevocationList.pem | jc --x509-crl
$ cat certificateRevocationList.der | jc --x509-crl
Usage (module):
import jc
result = jc.parse('x509_crl', x509_crl_file_output)
Schema:
{
"tbs_cert_list": {
"version": string,
"signature": {
"algorithm": string,
"parameters": string/null
},
"issuer": {
"organization_name": string,
"organizational_unit_name": string,
"common_name": string
},
"this_update": integer, # [1]
"next_update": integer, # [1]
"revoked_certificates": [
{
"user_certificate": integer,
"revocation_date": integer, # [1]
"crl_entry_extensions": [
{
"extn_id": string,
"critical": boolean,
"extn_value": string,
"extn_value_iso": string
},
"revocation_date_iso": string
}
],
"crl_extensions": [
{
"extn_id": string,
"critical": boolean,
"extn_value": array/object/string/integer # [2]
}
],
"this_update_iso": string,
"next_update_iso": string
},
"signature_algorithm": {
"algorithm": string,
"parameters": string/null
},
"signature": string # [0]
}
[0] in colon-delimited hex notation
[1] time-zone-aware (UTC) epoch timestamp
[2] See below for well-known Extension schemas:
Basic Constraints:
{
"extn_id": "basic_constraints",
"critical": boolean,
"extn_value": {
"ca": boolean,
"path_len_constraint": string/null
}
}
Key Usage:
{
"extn_id": "key_usage",
"critical": boolean,
"extn_value": [
string
]
}
Key Identifier:
{
"extn_id": "key_identifier",
"critical": boolean,
"extn_value": string # [0]
}
Authority Key Identifier:
{
"extn_id": "authority_key_identifier",
"critical": boolean,
"extn_value": {
"key_identifier": string, # [0]
"authority_cert_issuer": string/null,
"authority_cert_serial_number": string/null
}
}
Subject Alternative Name:
{
"extn_id": "subject_alt_name",
"critical": boolean,
"extn_value": [
string
]
}
Certificate Policies:
{
"extn_id": "certificate_policies",
"critical": boolean,
"extn_value": [
{
"policy_identifier": string,
"policy_qualifiers": [ array or null
{
"policy_qualifier_id": string,
"qualifier": string
}
]
}
]
}
Signed Certificate Timestamp List:
{
"extn_id": "signed_certificate_timestamp_list",
"critical": boolean,
"extn_value": string # [0]
}
Examples:
$ cat sample-crl.pem | jc --x509-crl -p
{
"tbs_cert_list": {
"version": "v2",
"signature": {
"algorithm": "sha1_rsa",
"parameters": null
},
"issuer": {
"organization_name": "Sample Signer Organization",
"organizational_unit_name": "Sample Signer Unit",
"common_name": "Sample Signer Cert"
},
"this_update": 1361183520,
"next_update": 1361184120,
"revoked_certificates": [
{
"user_certificate": 1341767,
"revocation_date": 1361182932,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "affiliation_changed"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:12+00:00"
},
{
"user_certificate": 1341768,
"revocation_date": 1361182942,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "certificate_hold"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:22+00:00"
},
{
"user_certificate": 1341769,
"revocation_date": 1361182952,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "superseded"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:32+00:00"
},
{
"user_certificate": 1341770,
"revocation_date": 1361182962,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "key_compromise"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:42+00:00"
},
{
"user_certificate": 1341771,
"revocation_date": 1361182971,
"crl_entry_extensions": [
{
"extn_id": "crl_reason",
"critical": false,
"extn_value": "cessation_of_operation"
},
{
"extn_id": "invalidity_date",
"critical": false,
"extn_value": 1361182920,
"extn_value_iso": "2013-02-18T10:22:00+00:00"
}
],
"revocation_date_iso": "2013-02-18T10:22:51+00:00"
}
],
"crl_extensions": [
{
"extn_id": "authority_key_identifier",
"critical": false,
"extn_value": {
"key_identifier": "be:12:01:cc:aa:ea:11:80:da:2e:ad:b2...",
"authority_cert_issuer": null,
"authority_cert_serial_number": null
}
},
{
"extn_id": "crl_number",
"critical": false,
"extn_value": 3
}
],
"this_update_iso": "2013-02-18T10:32:00+00:00",
"next_update_iso": "2013-02-18T10:42:00+00:00"
},
"signature_algorithm": {
"algorithm": "sha1_rsa",
"parameters": null
},
"signature": "42:21:be:81:f1:c3:79:76:66:5b:ce:21:13:8a:68:a..."
}
"""
from typing import List, Dict, Union
import jc.utils
from jc.parsers.asn1crypto import pem, crl, jc_global
from jc.parsers.x509_cert import _fix_objects
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = 'X.509 PEM and DER certificate revocation list file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
details = 'Using the asn1crypto library at https://github.com/wbond/asn1crypto/releases/tag/1.5.1'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['standard', 'file', 'string', 'binary']
__version__ = info.version
def parse(
data: Union[str, bytes],
raw: bool = False,
quiet: bool = False
) -> Dict:
"""
Main text parsing function
Parameters:
data: (string or bytes) text or binary data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Dictionary. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc_global.quiet = quiet # to inject quiet setting into asn1crypto library
raw_output: Dict = {}
if jc.utils.has_data(data):
# convert to bytes, if not already, for PEM detection since that's
# what pem.detect() needs. (cli.py will auto-convert to UTF-8 if it can)
try:
der_bytes = bytes(data, 'utf-8') # type: ignore
except TypeError:
der_bytes = data # type: ignore
if pem.detect(der_bytes):
for type_name, headers, der_bytes in pem.unarmor(der_bytes, multiple=True):
if type_name == 'X509 CRL':
crl_obj = crl.CertificateList.load(der_bytes)
break
else:
crl_obj = crl.CertificateList.load(der_bytes)
raw_output = _fix_objects(crl_obj.native)
return raw_output

View File

@@ -249,9 +249,6 @@ Examples:
}
]
"""
# import binascii
# from collections import OrderedDict
# from datetime import datetime
from typing import List, Dict, Union
import jc.utils
from jc.parsers.asn1crypto import pem, csr, jc_global

View File

@@ -87,7 +87,7 @@ from jc.exceptions import LibraryNotInstalled
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.7'
version = '1.8'
description = 'YAML file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -111,7 +111,6 @@ def _process(proc_data):
List of Dictionaries. Each dictionary represents a YAML document.
"""
# No further processing
return proc_data
@@ -148,17 +147,20 @@ def parse(data, raw=False, quiet=False):
# plugin code is incompatible with the pyoxidizer packager
YAML.official_plug_ins = lambda a: []
yaml = YAML(typ='safe')
# use the default `typ` to correctly load values that start with a literal "="
yaml = YAML(typ=None)
# modify the timestamp constructor to output datetime objects as
# strings since JSON does not support datetime objects
yaml.constructor.yaml_constructors['tag:yaml.org,2002:timestamp'] = \
yaml.constructor.yaml_constructors['tag:yaml.org,2002:str']
# modify the value constructor to output values starting with a
# literal "=" as a string.
yaml.constructor.yaml_constructors['tag:yaml.org,2002:value'] = \
yaml.constructor.yaml_constructors['tag:yaml.org,2002:str']
for document in yaml.load_all(data):
raw_output.append(document)
if raw:
return raw_output
else:
return _process(raw_output)
return raw_output if raw else _process(raw_output)

View File

@@ -1,7 +1,7 @@
"""jc - JSON Convert streaming utils"""
from functools import wraps
from typing import Dict, Tuple, Union, Iterable, Callable, TypeVar, cast, Any
from typing import Tuple, Union, Iterable, Callable, TypeVar, cast, Any
from .jc_types import JSONDictType
@@ -47,8 +47,9 @@ def stream_error(e: BaseException, line: str) -> JSONDictType:
def add_jc_meta(func: F) -> F:
"""
Decorator for streaming parsers to add stream_success and stream_error
objects. This simplifies the yield lines in the streaming parsers.
Decorator for streaming parsers to add `stream_success` and
`stream_error` objects. This simplifies the `yield` lines in the
streaming parsers.
With the decorator on parse():
@@ -82,7 +83,7 @@ def add_jc_meta(func: F) -> F:
successfully parse.
ignore_exceptions: (bool) continue processing lines and ignore
exceptions if True.
exceptions if `True`.
"""
@wraps(func)
def wrapper(*args, **kwargs):
@@ -109,8 +110,8 @@ def raise_or_yield(
line: str
) -> Tuple[BaseException, str]:
"""
Return the exception object and line string if ignore_exceptions is
True. Otherwise, re-raise the exception from the exception object with
Return the exception object and line string if `ignore_exceptions` is
`True`. Otherwise, re-raise the exception from the exception object with
an annotation.
"""
ignore_exceptions_msg = '... Use the ignore_exceptions option (-qq) to ignore streaming parser errors.'

View File

@@ -100,7 +100,7 @@ def error_message(message_lines: List[str]) -> None:
Returns:
None - just prints output to STDERR
None - just prints output to `STDERR`
"""
columns = shutil.get_terminal_size().columns
@@ -148,7 +148,7 @@ def compatibility(mod_name: str, compatible: List[str], quiet: bool = False) ->
the parser. compatible options:
linux, darwin, cygwin, win32, aix, freebsd
quiet: (bool) suppress compatibility message if True
quiet: (bool) suppress compatibility message if `True`
Returns:
@@ -169,7 +169,7 @@ def has_data(data: Union[str, bytes]) -> bool:
Checks if the string input contains data. If there are any
non-whitespace characters then return `True`, else return `False`.
For bytes, returns True if there is any data.
For bytes, returns `True` if there is any data.
Parameters:
@@ -177,9 +177,9 @@ def has_data(data: Union[str, bytes]) -> bool:
Returns:
Boolean True if input string (data) contains non-whitespace
characters, otherwise False. For bytes data, returns
True if there is any data, otherwise False.
Boolean `True` if input string (data) contains non-whitespace
characters, otherwise `False`. For bytes data, returns
`True` if there is any data, otherwise `False`.
"""
if isinstance(data, str):
return bool(data and not data.isspace())
@@ -365,7 +365,11 @@ def convert_to_bool(value: object) -> bool:
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
def convert_size_to_int(size: str, binary: bool = False) -> Optional[int]:
def convert_size_to_int(
size: str,
binary: bool = False,
posix_mode: bool = False,
decimal_bias: bool = False) -> Optional[int]:
"""
Parse a human readable data size and return the number of bytes.
@@ -375,6 +379,12 @@ def convert_size_to_int(size: str, binary: bool = False) -> Optional[int]:
binary: (boolean) `True` to use binary multiples of bytes
(base-2) for ambiguous unit symbols and names,
`False` to use decimal multiples of bytes (base-10).
posix_mode: (boolean) Treat one-letter units (k, m, g, etc.) as
binary.
decimal_bias: (boolean) `True` to treat slightly ambiguous two-
letter unit symbols ending in "i" (e.g. Ki, Gi) to
use decimal multiples of bytes (base-10). `False`
(default) to use binary multiples of bytes.
Returns:
integer/None Integer if successful conversion, otherwise None
@@ -394,6 +404,10 @@ def convert_size_to_int(size: str, binary: bool = False) -> Optional[int]:
1000
>>> convert_size_to_int('1 KiB')
1024
>>> convert_size_to_int('1 Ki')
1024
>>> convert_size_to_int('1 Ki', decimal_bias=True)
1000
>>> convert_size_to_int('1 KB', binary=True)
1024
>>> convert_size_to_int('1.5 GB')
@@ -441,17 +455,33 @@ def convert_size_to_int(size: str, binary: bool = False) -> Optional[int]:
# Convert plural units to singular units, for details:
# https://github.com/xolox/python-humanfriendly/issues/26
normalized_unit = normalized_unit.rstrip('s')
# Handle POSIX mode units where `k`, `m`, etc. are treated as binary
# https://www.gnu.org/software/coreutils/manual/html_node/Block-size.html
if len(normalized_unit) == 1 and posix_mode:
normalized_unit = normalized_unit + 'ib'
# Handle two-letter units (Ki, Gi, etc.) These are somewhat
# ambiguous, but are treated as binary by default. This can be
# changed with the `decimal_bias` parameter
if len(normalized_unit) == 2 and normalized_unit[1].lower() == 'i':
if decimal_bias:
normalized_unit = normalized_unit[0]
else:
normalized_unit = normalized_unit + 'b'
for unit in disk_size_units:
# First we check for unambiguous symbols (KiB, MiB, GiB, etc)
# and names (kibibyte, mebibyte, gibibyte, etc) because their
# handling is always the same.
if normalized_unit in (unit.binary.symbol.lower(), unit.binary.name.lower()):
return int(tokens[0] * unit.binary.divider)
# Now we will deal with ambiguous prefixes (K, M, G, etc),
# symbols (KB, MB, GB, etc) and names (kilobyte, megabyte,
# gigabyte, etc) according to the caller's preference.
if (normalized_unit in (unit.decimal.symbol.lower(), unit.decimal.name.lower()) or
normalized_unit.startswith(unit.decimal.symbol[0].lower())):
if (normalized_unit in (unit.decimal.symbol.lower(), unit.decimal.name.lower())
or normalized_unit.startswith(unit.decimal.symbol[0].lower())):
return int(tokens[0] * (unit.binary.divider if binary else unit.decimal.divider))
# We failed to parse the size specification.
return None
@@ -666,12 +696,14 @@ class timestamp:
{'id': 1700, 'format': '%m/%d/%Y, %I:%M:%S %p', 'locale': None}, # Windows english format wint non-UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC-0600)
{'id': 1705, 'format': '%m/%d/%Y, %I:%M:%S %p %Z', 'locale': None}, # Windows english format with UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC)
{'id': 1710, 'format': '%m/%d/%Y, %I:%M:%S %p UTC%z', 'locale': None}, # Windows english format with UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC+0000)
{'id': 1720, 'format': '%A, %B %d, %Y %I:%M:%S %p', 'locale': None}, # ipconfig cli output format: Thursday, June 22, 2023 10:39:04 AM
{'id': 1750, 'format': '%Y/%m/%d-%H:%M:%S.%f', 'locale': None}, # Google Big Table format with no timezone: 1970/01/01-01:00:00.000000
{'id': 1755, 'format': '%Y/%m/%d-%H:%M:%S.%f%z', 'locale': None}, # Google Big Table format with timezone: 1970/01/01-01:00:00.000000+00:00
{'id': 1760, 'format': '%Y-%m-%d %H:%M:%S%z', 'locale': None}, # certbot format with timezone: 2023-06-12 01:35:30+00:00
{'id': 1800, 'format': '%d/%b/%Y:%H:%M:%S %z', 'locale': None}, # Common Log Format: 10/Oct/2000:13:55:36 -0700
{'id': 2000, 'format': '%a %d %b %Y %I:%M:%S %p %Z', 'locale': None}, # en_US.UTF-8 local format (found in upower cli output): Tue 23 Mar 2021 04:12:11 PM UTC
{'id': 3000, 'format': '%a %d %b %Y %I:%M:%S %p', 'locale': None}, # en_US.UTF-8 local format with non-UTC tz (found in upower cli output): Tue 23 Mar 2021 04:12:11 PM IST
{'id': 3100, 'format': '%a %d %b %Y %I:%M:%S %p %z', 'locale': None}, # pacman format - append 00 to end to make it work: # Sat 11 May 2024 06:14:19 AM +0800
{'id': 3500, 'format': '%a, %d %b %Y %H:%M:%S %Z', 'locale': None}, # HTTP header time format (always GMT so assume UTC): Wed, 31 Jan 2024 00:39:28 GMT
{'id': 4000, 'format': '%A %d %B %Y %I:%M:%S %p %Z', 'locale': None}, # European-style local format (found in upower cli output): Tuesday 01 October 2019 12:50:41 PM UTC
{'id': 5000, 'format': '%A %d %B %Y %I:%M:%S %p', 'locale': None}, # European-style local format with non-UTC tz (found in upower cli output): Tuesday 01 October 2019 12:50:41 PM IST

View File

@@ -1,4 +1,4 @@
.TH jc 1 2024-06-09 1.25.3 "JSON Convert"
.TH jc 1 2025-10-12 1.25.6 "JSON Convert"
.SH NAME
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types,
and strings
@@ -52,6 +52,11 @@ Parsers:
\fB--airport-s\fP
`airport -s` command parser
.TP
.B
\fB--amixer\fP
`amixer` command parser
.TP
.B
\fB--apt-cache-show\fP
@@ -347,6 +352,11 @@ INI with duplicate key file parser
\fB--ip-address\fP
IPv4 and IPv6 Address string parser
.TP
.B
\fB--ipconfig\fP
`ipconfig` Windows command parser
.TP
.B
\fB--iptables\fP
@@ -477,6 +487,16 @@ M3U and M3U8 file parser
\fB--netstat\fP
`netstat` command parser
.TP
.B
\fB--net-localgroup\fP
`net localgroup` command parser
.TP
.B
\fB--net-user\fP
`net user` command parser
.TP
.B
\fB--nmcli\fP
@@ -507,6 +527,11 @@ openvpn-status.log file parser
\fB--os-release\fP
`/etc/os-release` file parser
.TP
.B
\fB--pacman\fP
`pacman` command parser
.TP
.B
\fB--passwd\fP
@@ -857,6 +882,11 @@ PLIST file parser
\fB--route\fP
`route` command parser
.TP
.B
\fB--route-print\fP
`route print` command parser
.TP
.B
\fB--rpm-qi\fP
@@ -1012,6 +1042,11 @@ TOML file parser
\fB--traceroute\fP
`traceroute` and `traceroute6` command parser
.TP
.B
\fB--traceroute-s\fP
`traceroute` and `traceroute6` command streaming parser
.TP
.B
\fB--tune2fs\fP
@@ -1092,6 +1127,11 @@ Version string parser
\fB--wc\fP
`wc` command parser
.TP
.B
\fB--wg-show\fP
`wg show` command parser
.TP
.B
\fB--who\fP
@@ -1102,6 +1142,11 @@ Version string parser
\fB--x509-cert\fP
X.509 PEM and DER certificate file parser
.TP
.B
\fB--x509-crl\fP
X.509 PEM and DER certificate revocation list file parser
.TP
.B
\fB--x509-csr\fP
@@ -1617,6 +1662,6 @@ Kelly Brazil (kellyjonbrazil@gmail.com)
https://github.com/kellyjonbrazil/jc
.SH COPYRIGHT
Copyright (c) 2019-2024 Kelly Brazil
Copyright (c) 2019-2025 Kelly Brazil
License: MIT License

View File

@@ -1,5 +1,5 @@
#!/bin/bash
# system should be in "America/Los_Angeles" timezone for all tests to pass
# ensure no local plugin parsers are installed for all tests to pass
# system should be in "America/Los_Angeles" (PST8PDT) timezone for all tests
# to pass ensure no local plugin parsers are installed for all tests to pass
python3 -m unittest -v
TZ=PST8PDT python3 -m unittest -v

View File

@@ -5,7 +5,7 @@ with open('README.md', 'r') as f:
setuptools.setup(
name='jc',
version='1.25.3',
version='1.25.6',
author='Kelly Brazil',
author_email='kellyjonbrazil@gmail.com',
description='Converts the output of popular command-line tools and file-types to JSON.',

View File

@@ -1,4 +1,3 @@
[![Tests](https://github.com/kellyjonbrazil/jc/workflows/Tests/badge.svg?branch=master)](https://github.com/kellyjonbrazil/jc/actions)
[![Pypi](https://img.shields.io/pypi/v/jc.svg)](https://pypi.org/project/jc/)
> Check out the `jc` Python [package documentation](https://github.com/kellyjonbrazil/jc/tree/master/docs) for developers

View File

@@ -1 +1 @@
[{"filesystem":"devtmpfs","size":1900000000,"used":0,"mounted_on":"/dev","available":1900000000,"use_percent":0},{"filesystem":"tmpfs","size":1900000000,"used":0,"mounted_on":"/dev/shm","available":1900000000,"use_percent":0},{"filesystem":"tmpfs","size":1900000000,"used":12000000,"mounted_on":"/run","available":1900000000,"use_percent":1},{"filesystem":"tmpfs","size":1900000000,"used":0,"mounted_on":"/sys/fs/cgroup","available":1900000000,"use_percent":0},{"filesystem":"/dev/mapper/centos-root","size":17000000000,"used":1800000000,"mounted_on":"/","available":16000000000,"use_percent":11},{"filesystem":"/dev/sda1","size":1014000000,"used":233000000,"mounted_on":"/boot","available":782000000,"use_percent":23},{"filesystem":"tmpfs","size":378000000,"used":0,"mounted_on":"/run/user/1000","available":378000000,"use_percent":0}]
[{"filesystem":"devtmpfs","size":2040109465,"used":0,"mounted_on":"/dev","available":2040109465,"use_percent":0},{"filesystem":"tmpfs","size":2040109465,"used":0,"mounted_on":"/dev/shm","available":2040109465,"use_percent":0},{"filesystem":"tmpfs","size":2040109465,"used":12582912,"mounted_on":"/run","available":2040109465,"use_percent":1},{"filesystem":"tmpfs","size":2040109465,"used":0,"mounted_on":"/sys/fs/cgroup","available":2040109465,"use_percent":0},{"filesystem":"/dev/mapper/centos-root","size":18253611008,"used":1932735283,"mounted_on":"/","available":17179869184,"use_percent":11},{"filesystem":"/dev/sda1","size":1063256064,"used":244318208,"mounted_on":"/boot","available":819986432,"use_percent":23},{"filesystem":"tmpfs","size":396361728,"used":0,"mounted_on":"/run/user/1000","available":396361728,"use_percent":0}]

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
[{"bssid": "00:19:a9:cd:c6:80", "interface": "wlan0", "freq": 2412, "capability": "ESS ShortPreamble ShortSlotTime (0x0421)", "ssid": "Cisco1240", "supported_rates": [1.0, 2.0, 5.5, 6.0, 9.0, 11.0, 12.0, 18.0], "erp": "<no flags>", "extended_supported_rates": [24.0, 36.0, 48.0, 54.0], "wmm": "Parameter version 1", "be": "CW 15-1023, AIFSN 3", "bk": "CW 15-1023, AIFSN 7", "vi": "CW 7-15, AIFSN 2, TXOP 3008 usec", "vo": "CW 3-7, AIFSN 2, TXOP 1504 usec", "tsf_usec": 2984923701, "beacon_interval_tus": 100, "signal_dbm": -45.0, "last_seen_ms": 429, "selected_rates": [1.0, 2.0, 5.5, 11.0], "ds_parameter_set_channel": 1}, {"bssid": "d0:d0:fd:69:ca:70", "interface": "wlan0", "freq": 2462, "capability": "ESS ShortPreamble ShortSlotTime (0x0421)", "ssid": "Cisco1250", "supported_rates": [1.0, 2.0, 5.5, 6.0, 9.0, 11.0, 12.0, 18.0], "erp": "<no flags>", "extended_supported_rates": [24.0, 36.0, 48.0, 54.0], "wmm": "Parameter version 1", "be": "CW 15-1023, AIFSN 3", "bk": "CW 15-1023, AIFSN 7", "vi": "CW 7-15, AIFSN 2, TXOP 3008 usec", "vo": "acm CW 3-7, AIFSN 2, TXOP 1504 usec", "tsf_usec": 2968648942, "beacon_interval_tus": 102, "signal_dbm": -70.0, "last_seen_ms": 328, "selected_rates": [1.0, 2.0, 5.5, 11.0], "ds_parameter_set_channel": 11}]
[{"bssid":"00:19:a9:cd:c6:80","interface":"wlan0","freq":2412,"capability":"ESS ShortPreamble ShortSlotTime (0x0421)","ssid":"Cisco1240","supported_rates":[1.0,2.0,5.5,6.0,9.0,11.0,12.0,18.0],"erp":"<no flags>","extended_supported_rates":[24.0,36.0,48.0,54.0],"wmm":"Parameter version 1","be":"CW 15-1023, AIFSN 3","bk":"CW 15-1023, AIFSN 7","vi":"CW 7-15, AIFSN 2, TXOP 3008 usec","vo":"CW 3-7, AIFSN 2, TXOP 1504 usec","tsf_usec":2984923701,"beacon_interval_tus":100,"signal_dbm":-45.0,"last_seen_ms":429,"selected_rates":[1.0,2.0,5.5,11.0],"ds_parameter_set_channel":1},{"bssid":"d0:d0:fd:69:ca:70","interface":"wlan0","freq":2462,"capability":"ESS ShortPreamble ShortSlotTime (0x0421)","ssid":"Cisco1250","supported_rates":[1.0,2.0,5.5,6.0,9.0,11.0,12.0,18.0],"erp":"<no flags>","extended_supported_rates":[24.0,36.0,48.0,54.0],"wmm":"Parameter version 1","be":"CW 15-1023, AIFSN 3","bk":"CW 15-1023, AIFSN 7","vi":"CW 7-15, AIFSN 2, TXOP 3008 usec","vo":"acm CW 3-7, AIFSN 2, TXOP 1504 usec","tsf_usec":2968648942,"beacon_interval_tus":102,"signal_dbm":-70.0,"last_seen_ms":328,"selected_rates":[1.0,2.0,5.5,11.0],"ds_parameter_set_channel":11}]

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
[{"bssid":"xx:xx:xx:xx:3e:41","interface":"wlan0-1","freq":2412,"capability":"ESS (0x1431)","information_elements_from_probe_response_frame_ssid":"Troubleshooting","information_elements_from_probe_response_frame_rsn":"Version: 1","information_elements_from_probe_response_frame_group_cipher":"CCMP","information_elements_from_probe_response_frame_pairwise_ciphers":"CCMP","information_elements_from_probe_response_frame_authentication_suites":"PSK","information_elements_from_probe_response_frame_capabilities":"16-PTKSA-RC 1-GTKSA-RC (0x000c)","ht_capabilities_capabilities":"0x1ef","ht_capabilities_max_amsdu_length":"3839 bytes","ht_capabilities_minimum_rx_ampdu_time_spacing":"No restriction (0x00)","ht_capabilities_ht_rx_mcs_rate_indexes_supported":"0-15","ht_operation_primary_channel":1,"ht_operation_secondary_channel_offset":"no secondary","ht_operation_sta_channel_width":"20 MHz","vht_capabilities_0x33800192_max_mpdu_length":11454,"vht_capabilities_0x33800192_supported_channel_width":"neither 160 nor 80 80","vht_rx_mcs_set_1_streams":"MCS 0-9","vht_rx_mcs_set_2_streams":"MCS 0-9","vht_rx_mcs_set_3_streams":"not supported","vht_rx_mcs_set_4_streams":"not supported","vht_rx_mcs_set_5_streams":"not supported","vht_rx_mcs_set_6_streams":"not supported","vht_rx_mcs_set_7_streams":"not supported","vht_rx_mcs_set_8_streams":"not supported","vht_rx_mcs_set_vht_rx_highest_supported":"780 Mbps","vht_tx_mcs_set_1_streams":"MCS 0-9","vht_tx_mcs_set_2_streams":"MCS 0-9","vht_tx_mcs_set_3_streams":"not supported","vht_tx_mcs_set_4_streams":"not supported","vht_tx_mcs_set_5_streams":"not supported","vht_tx_mcs_set_6_streams":"not supported","vht_tx_mcs_set_7_streams":"not supported","vht_tx_mcs_set_8_streams":"not supported","vht_tx_mcs_set_vht_tx_highest_supported":"780 Mbps","vht_tx_mcs_set_vht_extended_nss":"not supported","vht_operation_channel_width":"0 (20 or 40 MHz)","vht_operation_center_freq_segment_1":0,"vht_operation_center_freq_segment_2":0,"vht_operation_vht_basic_mcs_set":"0xfffa","he_mac_capabilities_0x010102000040_minimum_payload_size_of_128_bytes":1,"he_mac_capabilities_0x010102000040_he_phy_capabilities":"(0x06304c090c008008020c00):","he_mac_capabilities_0x010102000040_device_class":1,"he_mac_capabilities_0x010102000040_dcm_max_constellation":1,"he_mac_capabilities_0x010102000040_dcm_max_constellation_rx":1,"he_mac_capabilities_0x010102000040_beamformee_sts_<=_80mhz":3,"he_mac_capabilities_0x010102000040_max_nc":1,"he_mac_capabilities_0x010102000040_1_streams":"MCS 0-11","he_mac_capabilities_0x010102000040_2_streams":"MCS 0-11","he_mac_capabilities_0x010102000040_3_streams":"not supported","he_mac_capabilities_0x010102000040_4_streams":"not supported","he_mac_capabilities_0x010102000040_5_streams":"not supported","he_mac_capabilities_0x010102000040_6_streams":"not supported","he_mac_capabilities_0x010102000040_7_streams":"not supported","he_mac_capabilities_0x010102000040_8_streams":"not supported","tsf_usec":608162896731,"beacon_interval_tus":100,"signal_dbm":-54.0,"last_seen_ms":1410}]

109
tests/fixtures/centos-7.7/iw-scan2.out vendored Normal file
View File

@@ -0,0 +1,109 @@
BSS xx:xx:xx:xx:3e:41(on wlan0-1)
last seen: 4206.107s [boottime]
TSF: 608162896731 usec (7d, 00:56:02)
freq: 2412
beacon interval: 100 TUs
capability: ESS (0x1431)
signal: -54.00 dBm
last seen: 1410 ms ago
Information elements from Probe Response frame:
SSID: Troubleshooting
RSN: * Version: 1
* Group cipher: CCMP
* Pairwise ciphers: CCMP
* Authentication suites: PSK
* Capabilities: 16-PTKSA-RC 1-GTKSA-RC (0x000c)
HT capabilities:
Capabilities: 0x1ef
RX LDPC
HT20/HT40
SM Power Save disabled
RX HT20 SGI
RX HT40 SGI
TX STBC
RX STBC 1-stream
Max AMSDU length: 3839 bytes
No DSSS/CCK HT40
Maximum RX AMPDU length 65535 bytes (exponent: 0x003)
Minimum RX AMPDU time spacing: No restriction (0x00)
HT RX MCS rate indexes supported: 0-15
HT TX MCS rate indexes are undefined
HT operation:
* primary channel: 1
* secondary channel offset: no secondary
* STA channel width: 20 MHz
VHT capabilities:
VHT Capabilities (0x33800192):
Max MPDU length: 11454
Supported Channel Width: neither 160 nor 80 80
RX LDPC
TX STBC
RX antenna pattern consistency
TX antenna pattern consistency
VHT RX MCS set:
1 streams: MCS 0-9
2 streams: MCS 0-9
3 streams: not supported
4 streams: not supported
5 streams: not supported
6 streams: not supported
7 streams: not supported
8 streams: not supported
VHT RX highest supported: 780 Mbps
VHT TX MCS set:
1 streams: MCS 0-9
2 streams: MCS 0-9
3 streams: not supported
4 streams: not supported
5 streams: not supported
6 streams: not supported
7 streams: not supported
8 streams: not supported
VHT TX highest supported: 780 Mbps
VHT extended NSS: not supported
VHT operation:
* channel width: 0 (20 or 40 MHz)
* center freq segment 1: 0
* center freq segment 2: 0
* VHT basic MCS set: 0xfffa
HE capabilities:
HE MAC Capabilities (0x010102000040):
HTC HE Supported
Minimum Payload size of 128 bytes: 1
OM Control
A-MSDU in A-MPDU
HE PHY Capabilities: (0x06304c090c008008020c00):
HE40/2.4GHz
HE40/HE80/5GHz
Device Class: 1
LDPC Coding in Payload
STBC Tx <= 80MHz
STBC Rx <= 80MHz
Full Bandwidth UL MU-MIMO
DCM Max Constellation: 1
DCM Max Constellation Rx: 1
Beamformee STS <= 80Mhz: 3
PPE Threshold Present
Max NC: 1
20MHz in 40MHz HE PPDU 2.4GHz
TX 1024-QAM
RX 1024-QAM
HE RX MCS and NSS set <= 80 MHz
1 streams: MCS 0-11
2 streams: MCS 0-11
3 streams: not supported
4 streams: not supported
5 streams: not supported
6 streams: not supported
7 streams: not supported
8 streams: not supported
HE TX MCS and NSS set <= 80 MHz
1 streams: MCS 0-11
2 streams: MCS 0-11
3 streams: not supported
4 streams: not supported
5 streams: not supported
6 streams: not supported
7 streams: not supported
8 streams: not supported
PPE Threshold 0x19 0x1c 0xc7 0x71

View File

@@ -1 +1 @@
[{"name": "sda", "maj_min": "8:0", "rm": false, "size": "20G", "ro": false, "type": "disk", "mountpoint": null, "kname": "sda", "fstype": null, "label": null, "uuid": null, "partlabel": null, "partuuid": null, "ra": 4096, "model": "VMware Virtual S", "serial": null, "state": "running", "owner": "root", "group": "disk", "mode": "brw-rw----", "alignment": 0, "min_io": 512, "opt_io": 0, "phy_sec": 512, "log_sec": 512, "rota": true, "sched": "deadline", "rq_size": 128, "disc_aln": 0, "disc_gran": "0B", "disc_max": "0B", "disc_zero": false, "wsame": "32M", "wwn": null, "rand": true, "pkname": null, "hctl": "0:0:0:0", "tran": "spi", "rev": "1.0", "vendor": "VMware,"}, {"name": "sda1", "maj_min": "8:1", "rm": false, "size": "1G", "ro": false, "type": "part", "mountpoint": "/boot", "kname": "sda1", "fstype": "xfs", "label": null, "uuid": "05d927bb-5875-49e3-ada1-7f46cb31c932", "partlabel": null, "partuuid": null, "ra": 4096, "model": null, "serial": null, "state": null, "owner": "root", "group": "disk", "mode": "brw-rw----", "alignment": 0, "min_io": 512, "opt_io": 0, "phy_sec": 512, "log_sec": 512, "rota": true, "sched": "deadline", "rq_size": 128, "disc_aln": 0, "disc_gran": "0B", "disc_max": "0B", "disc_zero": false, "wsame": "32M", "wwn": null, "rand": true, "pkname": "sda", "hctl": null, "tran": null, "rev": null, "vendor": null}, {"name": "sda2", "maj_min": "8:2", "rm": false, "size": "19G", "ro": false, "type": "part", "mountpoint": null, "kname": "sda2", "fstype": "LVM2_member", "label": null, "uuid": "3klkIj-w1qk-DkJi-0XBJ-y3o7-i2Ac-vHqWBM", "partlabel": null, "partuuid": null, "ra": 4096, "model": null, "serial": null, "state": null, "owner": "root", "group": "disk", "mode": "brw-rw----", "alignment": 0, "min_io": 512, "opt_io": 0, "phy_sec": 512, "log_sec": 512, "rota": true, "sched": "deadline", "rq_size": 128, "disc_aln": 0, "disc_gran": "0B", "disc_max": "0B", "disc_zero": false, "wsame": "32M", "wwn": null, "rand": true, "pkname": "sda", "hctl": null, "tran": null, "rev": null, "vendor": null}, {"name": "centos-root", "maj_min": "253:0", "rm": false, "size": "17G", "ro": false, "type": "lvm", "mountpoint": "/", "kname": "dm-0", "fstype": "xfs", "label": null, "uuid": "07d718ef-950c-4e5b-98e0-42a1147b77d9", "partlabel": null, "partuuid": null, "ra": 4096, "model": null, "serial": null, "state": "running", "owner": "root", "group": "disk", "mode": "brw-rw----", "alignment": 0, "min_io": 512, "opt_io": 0, "phy_sec": 512, "log_sec": 512, "rota": true, "sched": null, "rq_size": 128, "disc_aln": 0, "disc_gran": "0B", "disc_max": "0B", "disc_zero": false, "wsame": "32M", "wwn": null, "rand": false, "pkname": "sda2", "hctl": null, "tran": null, "rev": null, "vendor": null}, {"name": "centos-swap", "maj_min": "253:1", "rm": false, "size": "2G", "ro": false, "type": "lvm", "mountpoint": "[SWAP]", "kname": "dm-1", "fstype": "swap", "label": null, "uuid": "615eb89d-bcbf-46ad-80e3-c483ef5c931f", "partlabel": null, "partuuid": null, "ra": 4096, "model": null, "serial": null, "state": "running", "owner": "root", "group": "disk", "mode": "brw-rw----", "alignment": 0, "min_io": 512, "opt_io": 0, "phy_sec": 512, "log_sec": 512, "rota": true, "sched": null, "rq_size": 128, "disc_aln": 0, "disc_gran": "0B", "disc_max": "0B", "disc_zero": false, "wsame": "32M", "wwn": null, "rand": false, "pkname": "sda2", "hctl": null, "tran": null, "rev": null, "vendor": null}, {"name": "sr0", "maj_min": "11:0", "rm": true, "size": "1024M", "ro": false, "type": "rom", "mountpoint": null, "kname": "sr0", "fstype": null, "label": null, "uuid": null, "partlabel": null, "partuuid": null, "ra": 128, "model": "VMware IDE CDR10", "serial": "10000000000000000001", "state": "running", "owner": "root", "group": "cdrom", "mode": "brw-rw----", "alignment": 0, "min_io": 512, "opt_io": 0, "phy_sec": 512, "log_sec": 512, "rota": true, "sched": "deadline", "rq_size": 128, "disc_aln": 0, "disc_gran": "0B", "disc_max": "0B", "disc_zero": false, "wsame": "0B", "wwn": null, "rand": true, "pkname": null, "hctl": "2:0:0:0", "tran": "ata", "rev": "1.00", "vendor": "NECVMWar"}]
[{"name":"sda","maj_min":"8:0","rm":false,"size":"20G","ro":false,"type":"disk","mountpoint":null,"kname":"sda","fstype":null,"label":null,"uuid":null,"partlabel":null,"partuuid":null,"ra":4096,"model":"VMware Virtual S","serial":null,"state":"running","owner":"root","group":"disk","mode":"brw-rw----","alignment":0,"min_io":512,"opt_io":0,"phy_sec":512,"log_sec":512,"rota":true,"sched":"deadline","rq_size":128,"disc_aln":0,"disc_gran":"0B","disc_max":"0B","disc_zero":false,"wsame":"32M","wwn":null,"rand":true,"pkname":null,"hctl":"0:0:0:0","tran":"spi","rev":"1.0","vendor":"VMware,","size_bytes":21474836480,"disc_gran_bytes":0,"disc_max_bytes":0,"wsame_bytes":33554432},{"name":"sda1","maj_min":"8:1","rm":false,"size":"1G","ro":false,"type":"part","mountpoint":"/boot","kname":"sda1","fstype":"xfs","label":null,"uuid":"05d927bb-5875-49e3-ada1-7f46cb31c932","partlabel":null,"partuuid":null,"ra":4096,"model":null,"serial":null,"state":null,"owner":"root","group":"disk","mode":"brw-rw----","alignment":0,"min_io":512,"opt_io":0,"phy_sec":512,"log_sec":512,"rota":true,"sched":"deadline","rq_size":128,"disc_aln":0,"disc_gran":"0B","disc_max":"0B","disc_zero":false,"wsame":"32M","wwn":null,"rand":true,"pkname":"sda","hctl":null,"tran":null,"rev":null,"vendor":null,"size_bytes":1073741824,"disc_gran_bytes":0,"disc_max_bytes":0,"wsame_bytes":33554432},{"name":"sda2","maj_min":"8:2","rm":false,"size":"19G","ro":false,"type":"part","mountpoint":null,"kname":"sda2","fstype":"LVM2_member","label":null,"uuid":"3klkIj-w1qk-DkJi-0XBJ-y3o7-i2Ac-vHqWBM","partlabel":null,"partuuid":null,"ra":4096,"model":null,"serial":null,"state":null,"owner":"root","group":"disk","mode":"brw-rw----","alignment":0,"min_io":512,"opt_io":0,"phy_sec":512,"log_sec":512,"rota":true,"sched":"deadline","rq_size":128,"disc_aln":0,"disc_gran":"0B","disc_max":"0B","disc_zero":false,"wsame":"32M","wwn":null,"rand":true,"pkname":"sda","hctl":null,"tran":null,"rev":null,"vendor":null,"size_bytes":20401094656,"disc_gran_bytes":0,"disc_max_bytes":0,"wsame_bytes":33554432},{"name":"centos-root","maj_min":"253:0","rm":false,"size":"17G","ro":false,"type":"lvm","mountpoint":"/","kname":"dm-0","fstype":"xfs","label":null,"uuid":"07d718ef-950c-4e5b-98e0-42a1147b77d9","partlabel":null,"partuuid":null,"ra":4096,"model":null,"serial":null,"state":"running","owner":"root","group":"disk","mode":"brw-rw----","alignment":0,"min_io":512,"opt_io":0,"phy_sec":512,"log_sec":512,"rota":true,"sched":null,"rq_size":128,"disc_aln":0,"disc_gran":"0B","disc_max":"0B","disc_zero":false,"wsame":"32M","wwn":null,"rand":false,"pkname":"sda2","hctl":null,"tran":null,"rev":null,"vendor":null,"size_bytes":18253611008,"disc_gran_bytes":0,"disc_max_bytes":0,"wsame_bytes":33554432},{"name":"centos-swap","maj_min":"253:1","rm":false,"size":"2G","ro":false,"type":"lvm","mountpoint":"[SWAP]","kname":"dm-1","fstype":"swap","label":null,"uuid":"615eb89d-bcbf-46ad-80e3-c483ef5c931f","partlabel":null,"partuuid":null,"ra":4096,"model":null,"serial":null,"state":"running","owner":"root","group":"disk","mode":"brw-rw----","alignment":0,"min_io":512,"opt_io":0,"phy_sec":512,"log_sec":512,"rota":true,"sched":null,"rq_size":128,"disc_aln":0,"disc_gran":"0B","disc_max":"0B","disc_zero":false,"wsame":"32M","wwn":null,"rand":false,"pkname":"sda2","hctl":null,"tran":null,"rev":null,"vendor":null,"size_bytes":2147483648,"disc_gran_bytes":0,"disc_max_bytes":0,"wsame_bytes":33554432},{"name":"sr0","maj_min":"11:0","rm":true,"size":"1024M","ro":false,"type":"rom","mountpoint":null,"kname":"sr0","fstype":null,"label":null,"uuid":null,"partlabel":null,"partuuid":null,"ra":128,"model":"VMware IDE CDR10","serial":"10000000000000000001","state":"running","owner":"root","group":"cdrom","mode":"brw-rw----","alignment":0,"min_io":512,"opt_io":0,"phy_sec":512,"log_sec":512,"rota":true,"sched":"deadline","rq_size":128,"disc_aln":0,"disc_gran":"0B","disc_max":"0B","disc_zero":false,"wsame":"0B","wwn":null,"rand":true,"pkname":null,"hctl":"2:0:0:0","tran":"ata","rev":"1.00","vendor":"NECVMWar","size_bytes":1073741824,"disc_gran_bytes":0,"disc_max_bytes":0,"wsame_bytes":0}]

View File

@@ -1 +1 @@
[{"name": "sda", "maj_min": "8:0", "rm": false, "size": "20G", "ro": false, "type": "disk", "mountpoint": null}, {"name": "sda1", "maj_min": "8:1", "rm": false, "size": "1G", "ro": false, "type": "part", "mountpoint": "/boot"}, {"name": "sda2", "maj_min": "8:2", "rm": false, "size": "19G", "ro": false, "type": "part", "mountpoint": null}, {"name": "centos-root", "maj_min": "253:0", "rm": false, "size": "17G", "ro": false, "type": "lvm", "mountpoint": "/"}, {"name": "centos-swap", "maj_min": "253:1", "rm": false, "size": "2G", "ro": false, "type": "lvm", "mountpoint": "[SWAP]"}, {"name": "sr0", "maj_min": "11:0", "rm": true, "size": "1024M", "ro": false, "type": "rom", "mountpoint": null}]
[{"name":"sda","maj_min":"8:0","rm":false,"size":"20G","ro":false,"type":"disk","mountpoint":null,"size_bytes":21474836480},{"name":"sda1","maj_min":"8:1","rm":false,"size":"1G","ro":false,"type":"part","mountpoint":"/boot","size_bytes":1073741824},{"name":"sda2","maj_min":"8:2","rm":false,"size":"19G","ro":false,"type":"part","mountpoint":null,"size_bytes":20401094656},{"name":"centos-root","maj_min":"253:0","rm":false,"size":"17G","ro":false,"type":"lvm","mountpoint":"/","size_bytes":18253611008},{"name":"centos-swap","maj_min":"253:1","rm":false,"size":"2G","ro":false,"type":"lvm","mountpoint":"[SWAP]","size_bytes":2147483648},{"name":"sr0","maj_min":"11:0","rm":true,"size":"1024M","ro":false,"type":"rom","mountpoint":null,"size_bytes":1073741824}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More