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

Compare commits

...

50 Commits

Author SHA1 Message Date
Kelly Brazil
4cd721be85 Dev v1.23.4 (#439)
* version bump

* fix regex for crlf line endings

* Completed Ip_route parser (#429)

* tests

* Merge pull request #398 from kellyjonbrazil/dev

Dev v1.23.2

* Merge pull request #398 from kellyjonbrazil/dev

Dev v1.23.2

---------

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>
Co-authored-by: Jjack3032 <julian.jackson@parsons.us>

* formatting

* doc update

* use splitlines

* formatting

* formatting

* Parser for `find` linux command (#434)

* Added find parser and tests for Centos 7.7 and Ubuntu 18.04

* Added a test file, changed logic, and included a case for permission denied returned by find.

* Added a few more lines to the tests

* Changed logic for setting values to null and updated test cases.

* doc update

* doc update

* Added proc_net_tcp parser (#421)

Co-authored-by: Kelly Brazil <kellyjonbrazil@gmail.com>

* clean up net_tcp parser

* add resolve.conf test files

* doc update

* add resolve.conf parser

* doc update

* add sortlist functionality

* add resolve.conf parser tests

* doc update

---------

Co-authored-by: Julian5555 <58196809+Julian5555@users.noreply.github.com>
Co-authored-by: Jjack3032 <julian.jackson@parsons.us>
Co-authored-by: solomonleang <124934439+solomonleang@users.noreply.github.com>
Co-authored-by: AlvinSolomon <41175627+AlvinSolomon@users.noreply.github.com>
2023-07-30 10:08:39 -07:00
Kelly Brazil
d58ca402a7 Merge pull request #432 from kellyjonbrazil/revert-430-find_parser
Revert "Added find parser and tests for Centos 7.7 and Ubuntu 18.04"
2023-06-23 15:40:06 +00:00
Kelly Brazil
5386879040 Revert "Added find parser and tests for Centos 7.7 and Ubuntu 18.04 (#430)"
This reverts commit f19a1f23a9.
2023-06-23 08:39:12 -07:00
solomonleang
f19a1f23a9 Added find parser and tests for Centos 7.7 and Ubuntu 18.04 (#430)
* Added find parser and tests for Centos 7.7 and Ubuntu 18.04

* Added a test file, changed logic, and included a case for permission denied returned by find.

* Added a few more lines to the tests

* Changed logic for setting values to null and updated test cases.
2023-06-23 08:38:56 -07:00
Kelly Brazil
5023e5be4c Dev v1.23.3 (#426)
* make certificate search more robust to different line endings

* use license_files instead of license_file which is deprecated

* version bump

* parsing extra options -e, -o, -p

* fix for extra opts and different field length at option -[aeop]

* test integration for extra opts -e -o -p

* formatting and use ast.literal_eval instead of eval

* doc update

* doc update

* Add a parser to parse mounted encrypted veracrypt volumes (fixes #403)

* update compatibility warning message

* netstat windows parser

* tests

* Windows route parser

* tests

* id should be a string

* add veracrypt parser and docs

* formatting

* doc update

* lsattr parser

* Update test_lsattr.py

* changed keys to lowercase

* changed info

* support missing data for stat

* doc update

* doc update

* doc update

* ensure compatibility warning prints even with no data

* improve compatibility message

* add support for dig +nsid option

* New parser: srt (#415)

* srt parser

* changed the parser to support more complex cases

* doc updates

* Adding certificate request parser (#416)

* Adding certificate request parser

* Adding the CSR type for Windows-style CSR

---------

Co-authored-by: Stg22 <stephane.for.test@gmail.com>

* doc update

* add csr tests

* Last -x (#422)

* Refactored the parser

* last -x support

* doc update

* fix for ping on linux with missing hostname

* allow less strict email decoding with a warning.

* doc update

* use explicit ascii decode with backslashreplace

* doc update

* use jc warning function instead of print for warning message

* last -x shutdown fix (#423)

* inject quiet setting into asn1crypto library

* Parse appearance and modalias lines for mouse devices (fixes #419) (#425)

The bluetoothctl device parser is implemented so that it aborts the parsing
process immediately returning what it has collected so far. This is because
the parser should work in hydrid way to support outputs comming from bluetoothctl
devices and bluetoothctl info calls.

* doc update

* doc update

---------

Co-authored-by: gerd <gerd.augstein@gmail.com>
Co-authored-by: Jake Ob <iakopap@gmail.com>
Co-authored-by: Mevaser <mevaser.rotner@gmail.com>
Co-authored-by: M.R <69431152+YeahItsMeAgain@users.noreply.github.com>
Co-authored-by: Stg22 <46686290+Stg22@users.noreply.github.com>
Co-authored-by: Stg22 <stephane.for.test@gmail.com>
2023-06-21 15:48:23 -07:00
Kelly Brazil
5527d22459 Merge pull request #398 from kellyjonbrazil/dev
Dev v1.23.2
2023-04-30 10:40:21 -07:00
Kelly Brazil
9d567c2e70 python 3.6 fix 2023-04-30 10:13:00 -07:00
Kelly Brazil
39c03a15d5 doc update 2023-04-30 09:34:22 -07:00
Kelly Brazil
5e6d2562f9 add certbot command parser 2023-04-29 16:34:23 -07:00
Kelly Brazil
7009d5a014 fix for incorrect insertion and deletion parsing 2023-04-22 14:04:00 -07:00
Kelly Brazil
42f9ddabb9 change bluetoothctl compatibility 2023-04-21 14:22:23 -07:00
Kelly Brazil
30efb5afc0 fix for multiline description 2023-04-18 11:23:49 -07:00
Kelly Brazil
786dc76c09 fix for battery not charging message 2023-04-17 18:12:57 -07:00
Kelly Brazil
fc48874a5d pytest warning fixes 2023-04-17 15:15:40 -07:00
Kelly Brazil
67164e7b23 fix mypy issues 2023-04-16 18:50:40 -07:00
Kelly Brazil
3db9774ac6 doc update 2023-04-16 11:46:21 -07:00
Kelly Brazil
03aef93d9a Merge pull request #392 from tzeikob/bluetoothctl-parser
Add parser for the bluetoothctl utility
2023-04-16 11:39:54 -07:00
Jake Ob
9b7e3de3ed Fix broken unit tests for the bluetoothctl parser 2023-04-16 20:52:29 +03:00
Jake Ob
291b6b061a Parse numeric fields to their actual numeric value 2023-04-16 20:42:48 +03:00
Jake Ob
08496533e2 Process data only if data is given 2023-04-16 20:19:15 +03:00
Jake Ob
f8dceb5046 Fix minor typo in documentation 2023-04-16 20:15:24 +03:00
Jake Ob
240ed4047f Add parser for the bluetoothctl utility 2023-04-14 18:23:45 +03:00
Kelly Brazil
0bf6f7cd7c fix incorrect variable parsing when wildcard schedule 2023-04-02 13:18:16 -07:00
Kelly Brazil
313bd86e3e doc update 2023-04-02 12:57:56 -07:00
Kelly Brazil
56259d5605 fix for dashes in name 2023-04-02 12:57:50 -07:00
Kelly Brazil
8a4885c1fe Merge pull request #384 from six3six/patch-1
Fix iwconfig re_interface regex
2023-03-31 08:40:00 -07:00
Louis Desplanche
5b06f84917 Fix iwconfig re_interface regex
Small fix of re_interface in iwconfig.py.
When the network SSID contains a "-" character, the network interface was ignored.
2023-03-31 11:15:31 +02:00
Kelly Brazil
37a1428914 Merge pull request #381 from kellyjonbrazil/dev
Dev v1.23.1
2023-03-23 20:16:11 -04:00
Kelly Brazil
bff065daf3 doc update 2023-03-23 16:41:50 -07:00
Kelly Brazil
c60d899f31 ignore non-parser-plugin python files in plugin directory 2023-03-23 16:40:13 -07:00
Kelly Brazil
fb7c390506 add lsusb tests 2023-03-23 06:43:06 -07:00
Kelly Brazil
22afb69573 doc update 2023-03-22 16:47:45 -07:00
Kelly Brazil
be51304c9c add support for CDC MBIM and CDC MBIM Extended 2023-03-22 16:43:57 -07:00
Kelly Brazil
44f83d800f doc update 2023-03-19 12:49:25 -07:00
Kelly Brazil
bf07973d90 doc update 2023-03-19 12:39:28 -07:00
Kelly Brazil
11e94b686c mypy fix 2023-03-18 15:47:19 -07:00
Kelly Brazil
c68bf674a1 add py.typed file to wheel 2023-03-18 15:43:22 -07:00
Kelly Brazil
dbbc310082 Merge pull request #378 from jwilk-forks/proc-status-umask
Make umask optional in /proc/PID/status
2023-03-18 22:37:13 +00:00
Kelly Brazil
e861f4a597 Merge pull request #379 from jwilk-forks/spelling
Fix typo
2023-03-18 22:25:21 +00:00
Kelly Brazil
049e93707c doc update 2023-03-18 15:16:02 -07:00
Kelly Brazil
164294ecb7 add integer and float conversions 2023-03-18 15:14:59 -07:00
Jakub Wilk
22ef489795 fix typo 2023-03-18 11:07:15 +01:00
Jakub Wilk
8f9d650f6c make umask optional in /proc/PID/status
Zombie processes don't have the umask field available.
2023-03-18 10:28:05 +01:00
Kelly Brazil
7e134a63bd add support for the timesync-status option 2023-03-15 06:58:31 -07:00
Kelly Brazil
9b5c25cb5b fix for rtc configured as UTC 2023-03-13 19:47:38 -07:00
Kelly Brazil
894946b207 doc update 2023-03-05 10:07:23 -08:00
Kelly Brazil
92ad2068db version bump 2023-03-05 10:06:54 -08:00
Kelly Brazil
59662a1500 fix for lines that start with tab 2023-03-05 10:04:00 -08:00
Kelly Brazil
125b88a2ca Merge pull request #371 from kellyjonbrazil/master
sync to dev
2023-03-05 17:59:09 +00:00
Kelly Brazil
79fce8c769 doc update 2023-02-27 17:37:49 -08:00
218 changed files with 10441 additions and 350 deletions

View File

@@ -1,5 +1,47 @@
jc changelog
20230730 v1.23.4
- Add `/etc/resolve.conf` file parser
- Add `/proc/net/tcp` and `/proc/net/tcp6` file parser
- Add `find` command parser
- Add `ip route` command parser
- Fix `certbot` command parser to be more robust with different line endings
20230621 v1.23.3
- Add `lsattr` command parser
- Add `srt` file parser
- Add `veracrypt` command parser
- Add X509 Certificate Request file parser
- Enhance X509 Certificate parser to allow non-compliant email addresses with a warning
- Enhance `dig` command parser to support the `+nsid` option
- Enhance `last` and `lastb` command parser to support the `-x` option
- Enhance `route` command parser to add Windows support
- Enhnace `netstat` command parser to add Windows support
- Enhance `ss` command parser to support extended options
- Enhance the compatibility warning message
- Fix `bluetoothctl` command parser for some mouse devices
- Fix `ping` command parsers for output with missing hostname
- Fix `stat` command parser for older versions that may not contain all fields
- Fix deprecated option in `setup.cfg`
20230429 v1.23.2
- Add `bluetoothctl` command parser
- Add `certbot` command parser for `certificates` and `show_account` options
- Fix `acpi` command parser for "Not charging" battery status lines
- Fix `iwconfig` command parser for SSIDs with dashes in the name
- Fix `crontab` command parsers for incorrect variable parsing in some cases
- Fix `git-log` and `git-log-s` command parsers for incorrect insertion/deletion parsing
- Fix `ufw-appinfo` command parser for parsing errors on multiline description fields
- Fix pytest warnings
20230323 v1.23.1
- Fix `zpool-status` command parser for lines that start with tab
- Fix `timedatectl` command parser when RTC set to local
- Fix to ensure `py.typed` file is included in the package wheel
- Fix `lsusb` command parser to support CDC MBIM and CDC MBIM Extended fields
- Add support for the `timesync-status` for the `timedatectl` command parser
- Fix to ignore non-parser-plugins in the parser plugin directory
20230227 v1.23.0
- Add input slicing as a `jc` command-line option
- Add `ssh` configuration file parser

View File

@@ -4551,6 +4551,57 @@ cat entrust.pem | jc --x509-cert -p
}
]
```
### X.509 PEM and DER certificate request files
```bash
cat myserver.csr | jc --x509-csr -p
```
```json
[
{
"certification_request_info": {
"version": "v1",
"subject": {
"common_name": "myserver.for.example"
},
"subject_pk_info": {
"algorithm": {
"algorithm": "ec",
"parameters": "secp256r1"
},
"public_key": "04:40:33:c0:91:8f:e9:46:ea:d0:dc:d0:f9:63:2c:a4:35:1f:0f:54:c8:a9:9b:e3:9e:d4:f3:64:b8:60:cc:7f:39:75:dd:a7:61:31:02:7c:9e:89:c6:db:45:15:f2:5f:b0:65:29:0b:42:d2:6e:c2:ea:a6:23:bd:fc:65:e5:7d:4e"
},
"attributes": [
{
"type": "extension_request",
"values": [
[
{
"extn_id": "extended_key_usage",
"critical": false,
"extn_value": [
"server_auth"
]
},
{
"extn_id": "subject_alt_name",
"critical": false,
"extn_value": [
"myserver.for.example"
]
}
]
]
}
]
},
"signature_algorithm": {
"algorithm": "sha384_ecdsa",
"parameters": null
},
"signature": "30:45:02:20:77:ac:5b:51:bf:c5:f5:43:02:52:ae:66:8a:fe:95:98:98:98:a9:45:34:31:08:ff:2c:cc:92:d9:1c:70:28:74:02:21:00:97:79:7b:e7:45:18:76:cf:d7:3b:79:34:56:d2:69:b5:73:41:9b:8a:b7:ad:ec:80:23:c1:2f:64:da:e5:28:19"
}
]
```
### XML files
```bash
cat cd_catalog.xml

View File

@@ -5,11 +5,13 @@
> Try the `jc` [web demo](https://jc-web.onrender.com/) and [REST API](https://github.com/kellyjonbrazil/jc-restapi)
> JC is [now available](https://galaxy.ansible.com/community/general) as an
> `jc` is [now available](https://galaxy.ansible.com/community/general) as an
Ansible filter plugin in the `community.general` collection. See this
[blog post](https://blog.kellybrazil.com/2020/08/30/parsing-command-output-in-ansible-with-jc/)
for an example.
> Looking for something like `jc` but lower-level? Check out [regex2json](https://gitlab.com/tozd/regex2json).
# JC
JSON Convert
@@ -161,9 +163,11 @@ option.
| `--asciitable` | ASCII and Unicode table parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/asciitable) |
| `--asciitable-m` | multi-line ASCII and Unicode table parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/asciitable_m) |
| `--blkid` | `blkid` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/blkid) |
| `--bluetoothctl` | `bluetoothctl` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/bluetoothctl) |
| `--cbt` | `cbt` (Google Bigtable) command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cbt) |
| `--cef` | CEF string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cef) |
| `--cef-s` | CEF string streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cef_s) |
| `--certbot` | `certbot` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/certbot) |
| `--chage` | `chage --list` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/chage) |
| `--cksum` | `cksum` and `sum` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cksum) |
| `--clf` | Common and Combined Log Format file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/clf) |
@@ -183,6 +187,7 @@ option.
| `--email-address` | Email Address string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/email_address) |
| `--env` | `env` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/env) |
| `--file` | `file` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/file) |
| `--find` | `find` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/find) |
| `--findmnt` | `findmnt` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/findmnt) |
| `--finger` | `finger` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/finger) |
| `--free` | `free` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/free) |
@@ -206,6 +211,7 @@ option.
| `--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) |
| `--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) |
| `--iwconfig` | `iwconfig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iwconfig) |
| `--jar-manifest` | Java MANIFEST.MF file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/jar_manifest) |
@@ -215,6 +221,7 @@ option.
| `--last` | `last` and `lastb` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/last) |
| `--ls` | `ls` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ls) |
| `--ls-s` | `ls` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ls_s) |
| `--lsattr` | `lsattr` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsattr) |
| `--lsblk` | `lsblk` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsblk) |
| `--lsmod` | `lsmod` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsmod) |
| `--lsof` | `lsof` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsof) |
@@ -243,6 +250,7 @@ option.
| `--postconf` | `postconf -M` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/postconf) |
| `--proc` | `/proc/` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/proc) |
| `--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) |
| `--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) |
@@ -250,6 +258,7 @@ option.
| `--semver` | Semantic Version string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/semver) |
| `--sfdisk` | `sfdisk` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/sfdisk) |
| `--shadow` | `/etc/shadow` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/shadow) |
| `--srt` | SRT file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/srt) |
| `--ss` | `ss` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ss) |
| `--ssh-conf` | `ssh` config file and `ssh -G` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ssh_conf) |
| `--sshd-conf` | `sshd` config file and `sshd -T` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/sshd_conf) |
@@ -283,12 +292,14 @@ option.
| `--uptime` | `uptime` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/uptime) |
| `--url` | URL string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/url) |
| `--ver` | Version string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ver) |
| `--veracrypt` | `veracrypt` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/veracrypt) |
| `--vmstat` | `vmstat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/vmstat) |
| `--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) |
| `--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-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) |
| `--yaml` | YAML file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/yaml) |
@@ -533,20 +544,22 @@ for item in result:
print(item["filename"])
```
### Custom Parsers
Custom local parser plugins may be placed in a `jc/jcparsers` folder in your
local **"App data directory"**:
### Parser Plugins
Parser plugins may be placed in a `jc/jcparsers` folder in your local
**"App data directory"**:
- Linux/unix: `$HOME/.local/share/jc/jcparsers`
- macOS: `$HOME/Library/Application Support/jc/jcparsers`
- Windows: `$LOCALAPPDATA\jc\jc\jcparsers`
Local parser plugins are standard python module files. Use the
Parser plugins are standard python module files. Use the
[`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py)
or [`jc/parsers/foo_s.py (streaming)`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo_s.py)
parser as a template and simply place a `.py` file in the `jcparsers` subfolder.
Any dependencies can be placed in the `jc` folder above `jcparsers` and can
be imported in the parser code.
Local plugin filenames must be valid python module names and therefore must
Parser plugin filenames must be valid python module names and therefore must
start with a letter and consist entirely of alphanumerics and underscores.
Local plugins may override default parsers.
@@ -604,7 +617,7 @@ they are run on an unsupported platform. To see all parser information,
including compatibility, run `jc -ap`.
You may still use a parser on an unsupported platform - for example, you may
want to parse a file with linux `lsof` output on an macOS or Windows laptop. In
want to parse a file with linux `lsof` output on a macOS or Windows laptop. In
that case you can suppress the warning message with the `-q` cli option or the
`quiet=True` function parameter in `parse()`:

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 arp blkid cbt chage cksum crontab date df dig dmidecode dpkg du env file findmnt finger free git gpg hciconfig id ifconfig iostat iptables iw iwconfig jobs last lastb ls lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat netstat nmcli 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 sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 udevadm ufw uname update-alternatives upower uptime vdir vmstat w wc who xrandr zipinfo zpool)
jc_parsers=(--acpi --airport --airport-s --arp --asciitable --asciitable-m --blkid --cbt --cef --cef-s --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --date --datetime-iso --df --dig --dir --dmidecode --dpkg-l --du --email-address --env --file --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --hosts --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --iptables --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --last --ls --ls-s --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --netstat --nmcli --ntpq --openvpn --os-prober --passwd --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --plist --postconf --proc --proc-buddyinfo --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-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 --route --rpm-qi --rsync --rsync-s --semver --sfdisk --shadow --ss --ssh-conf --sshd-conf --stat --stat-s --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 --udevadm --ufw --ufw-appinfo --uname --update-alt-gs --update-alt-q --upower --uptime --url --ver --vmstat --vmstat-s --w --wc --who --x509-cert --xml --xrandr --yaml --zipinfo --zpool-iostat --zpool-status)
jc_commands=(acpi airport arp blkid bluetoothctl cbt certbot chage cksum crontab date df dig dmidecode dpkg du env file findmnt finger free git gpg hciconfig id ifconfig iostat ip iptables iw iwconfig jobs last lastb ls lsattr lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat netstat nmcli 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 sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 udevadm ufw uname update-alternatives upower uptime vdir veracrypt vmstat w wc who xrandr zipinfo zpool)
jc_parsers=(--acpi --airport --airport-s --arp --asciitable --asciitable-m --blkid --bluetoothctl --cbt --cef --cef-s --certbot --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --date --datetime-iso --df --dig --dir --dmidecode --dpkg-l --du --email-address --env --file --find --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --hosts --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --iptables --ip-route --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --last --ls --ls-s --lsattr --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --netstat --nmcli --ntpq --openvpn --os-prober --passwd --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --plist --postconf --proc --proc-buddyinfo --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 --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 --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_options=(--force-color -C --debug -d --monochrome -m --meta-out -M --pretty -p --quiet -q --raw -r --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,13 +9,15 @@ _jc() {
jc_help_options jc_help_options_describe \
jc_special_options jc_special_options_describe
jc_commands=(acpi airport arp blkid cbt chage cksum crontab date df dig dmidecode dpkg du env file findmnt finger free git gpg hciconfig id ifconfig iostat iptables iw iwconfig jobs last lastb ls lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat netstat nmcli 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 sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 udevadm ufw uname update-alternatives upower uptime vdir vmstat w wc who xrandr zipinfo zpool)
jc_commands=(acpi airport arp blkid bluetoothctl cbt certbot chage cksum crontab date df dig dmidecode dpkg du env file findmnt finger free git gpg hciconfig id ifconfig iostat ip iptables iw iwconfig jobs last lastb ls lsattr lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat netstat nmcli 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 sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 udevadm ufw uname update-alternatives upower uptime vdir veracrypt vmstat w wc who xrandr zipinfo zpool)
jc_commands_describe=(
'acpi:run "acpi" command with magic syntax.'
'airport:run "airport" command with magic syntax.'
'arp:run "arp" command with magic syntax.'
'blkid:run "blkid" command with magic syntax.'
'bluetoothctl:run "bluetoothctl" command with magic syntax.'
'cbt:run "cbt" command with magic syntax.'
'certbot:run "certbot" command with magic syntax.'
'chage:run "chage" command with magic syntax.'
'cksum:run "cksum" command with magic syntax.'
'crontab:run "crontab" command with magic syntax.'
@@ -36,6 +38,7 @@ _jc() {
'id:run "id" command with magic syntax.'
'ifconfig:run "ifconfig" command with magic syntax.'
'iostat:run "iostat" command with magic syntax.'
'ip:run "ip" 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.'
@@ -43,6 +46,7 @@ _jc() {
'last:run "last" command with magic syntax.'
'lastb:run "lastb" command with magic syntax.'
'ls:run "ls" command with magic syntax.'
'lsattr:run "lsattr" command with magic syntax.'
'lsblk:run "lsblk" command with magic syntax.'
'lsmod:run "lsmod" command with magic syntax.'
'lsof:run "lsof" command with magic syntax.'
@@ -96,6 +100,7 @@ _jc() {
'upower:run "upower" command with magic syntax.'
'uptime:run "uptime" command with magic syntax.'
'vdir:run "vdir" command with magic syntax.'
'veracrypt:run "veracrypt" command with magic syntax.'
'vmstat:run "vmstat" command with magic syntax.'
'w:run "w" command with magic syntax.'
'wc:run "wc" command with magic syntax.'
@@ -104,7 +109,7 @@ _jc() {
'zipinfo:run "zipinfo" command with magic syntax.'
'zpool:run "zpool" command with magic syntax.'
)
jc_parsers=(--acpi --airport --airport-s --arp --asciitable --asciitable-m --blkid --cbt --cef --cef-s --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --date --datetime-iso --df --dig --dir --dmidecode --dpkg-l --du --email-address --env --file --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --hosts --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --iptables --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --last --ls --ls-s --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --netstat --nmcli --ntpq --openvpn --os-prober --passwd --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --plist --postconf --proc --proc-buddyinfo --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-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 --route --rpm-qi --rsync --rsync-s --semver --sfdisk --shadow --ss --ssh-conf --sshd-conf --stat --stat-s --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 --udevadm --ufw --ufw-appinfo --uname --update-alt-gs --update-alt-q --upower --uptime --url --ver --vmstat --vmstat-s --w --wc --who --x509-cert --xml --xrandr --yaml --zipinfo --zpool-iostat --zpool-status)
jc_parsers=(--acpi --airport --airport-s --arp --asciitable --asciitable-m --blkid --bluetoothctl --cbt --cef --cef-s --certbot --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --date --datetime-iso --df --dig --dir --dmidecode --dpkg-l --du --email-address --env --file --find --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --hosts --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --iptables --ip-route --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --last --ls --ls-s --lsattr --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --netstat --nmcli --ntpq --openvpn --os-prober --passwd --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --plist --postconf --proc --proc-buddyinfo --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 --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 --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_describe=(
'--acpi:`acpi` command parser'
'--airport:`airport -I` command parser'
@@ -113,9 +118,11 @@ _jc() {
'--asciitable:ASCII and Unicode table parser'
'--asciitable-m:multi-line ASCII and Unicode table parser'
'--blkid:`blkid` command parser'
'--bluetoothctl:`bluetoothctl` command parser'
'--cbt:`cbt` (Google Bigtable) command parser'
'--cef:CEF string parser'
'--cef-s:CEF string streaming parser'
'--certbot:`certbot` command parser'
'--chage:`chage --list` command parser'
'--cksum:`cksum` and `sum` command parser'
'--clf:Common and Combined Log Format file parser'
@@ -135,6 +142,7 @@ _jc() {
'--email-address:Email Address string parser'
'--env:`env` command parser'
'--file:`file` command parser'
'--find:`find` command parser'
'--findmnt:`findmnt` command parser'
'--finger:`finger` command parser'
'--free:`free` command parser'
@@ -158,6 +166,7 @@ _jc() {
'--iostat-s:`iostat` command streaming parser'
'--ip-address:IPv4 and IPv6 Address string parser'
'--iptables:`iptables` command parser'
'--ip-route:`ip route` command parser'
'--iw-scan:`iw dev [device] scan` command parser'
'--iwconfig:`iwconfig` command parser'
'--jar-manifest:Java MANIFEST.MF file parser'
@@ -167,6 +176,7 @@ _jc() {
'--last:`last` and `lastb` command parser'
'--ls:`ls` command parser'
'--ls-s:`ls` command streaming parser'
'--lsattr:`lsattr` command parser'
'--lsblk:`lsblk` command parser'
'--lsmod:`lsmod` command parser'
'--lsof:`lsof` command parser'
@@ -233,6 +243,7 @@ _jc() {
'--proc-net-packet:`/proc/net/packet` file parser'
'--proc-net-protocols:`/proc/net/protocols` file parser'
'--proc-net-route:`/proc/net/route` file parser'
'--proc-net-tcp:`/proc/net/tcp` and `/proc/net/tcp6` file parser'
'--proc-net-unix:`/proc/net/unix` file parser'
'--proc-pid-fdinfo:`/proc/<pid>/fdinfo/<fd>` file parser'
'--proc-pid-io:`/proc/<pid>/io` file parser'
@@ -244,6 +255,7 @@ _jc() {
'--proc-pid-statm:`/proc/<pid>/statm` file parser'
'--proc-pid-status:`/proc/<pid>/status` file parser'
'--ps:`ps` command parser'
'--resolve-conf:`/etc/resolve.conf` file parser'
'--route:`route` command parser'
'--rpm-qi:`rpm -qi` command parser'
'--rsync:`rsync` command parser'
@@ -251,6 +263,7 @@ _jc() {
'--semver:Semantic Version string parser'
'--sfdisk:`sfdisk` command parser'
'--shadow:`/etc/shadow` file parser'
'--srt:SRT file parser'
'--ss:`ss` command parser'
'--ssh-conf:`ssh` config file and `ssh -G` command parser'
'--sshd-conf:`sshd` config file and `sshd -T` command parser'
@@ -284,12 +297,14 @@ _jc() {
'--uptime:`uptime` command parser'
'--url:URL string parser'
'--ver:Version string parser'
'--veracrypt:`veracrypt` command parser'
'--vmstat:`vmstat` command parser'
'--vmstat-s:`vmstat` command streaming parser'
'--w:`w` command parser'
'--wc:`wc` command parser'
'--who:`who` command parser'
'--x509-cert:X.509 PEM and DER certificate file parser'
'--x509-csr:X.509 PEM and DER certificate request file parser'
'--xml:XML file parser'
'--xrandr:`xrandr` command parser'
'--yaml:YAML file parser'

View File

@@ -250,4 +250,4 @@ Returns:
### Parser Information
Compatibility: linux
Version 1.5 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.6 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -0,0 +1,133 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.bluetoothctl"></a>
# jc.parsers.bluetoothctl
jc - JSON Convert `bluetoothctl` command output parser
Supports the following `bluetoothctl` subcommands:
- `bluetoothctl list`
- `bluetoothctl show`
- `bluetoothctl show <ctrl>`
- `bluetoothctl devices`
- `bluetoothctl info <dev>`
Usage (cli):
$ bluetoothctl info <dev> | jc --bluetoothctl
or
$ jc bluetoothctl info <dev>
Usage (module):
import jc
result = jc.parse('bluetoothctl', bluetoothctl_command_output)
Schema:
Because bluetoothctl is handling two main entities, controllers and devices,
the schema is shared between them. Most of the fields are common between
a controller and a device but there might be fields corresponding to one entity.
Controller:
[
{
"name": string,
"is_default": boolean,
"is_public": boolean,
"is_random": boolean,
"address": string,
"alias": string,
"class": string,
"powered": string,
"discoverable": string,
"discoverable_timeout": string,
"pairable": string,
"modalias": string,
"discovering": string,
"uuids": array
}
]
Device:
[
{
"name": string,
"is_public": boolean,
"is_random": boolean,
"address": string,
"alias": string,
"appearance": string,
"class": string,
"icon": string,
"paired": string,
"bonded": string,
"trusted": string,
"blocked": string,
"connected": string,
"legacy_pairing": string,
"rssi": int,
"txpower": int,
"uuids": array,
"modalias": string
}
]
Examples:
$ bluetoothctl info EB:06:EF:62:B3:19 | jc --bluetoothctl -p
[
{
"address": "22:06:33:62:B3:19",
"is_public": true,
"name": "TaoTronics TT-BH336",
"alias": "TaoTronics TT-BH336",
"class": "0x00240455",
"icon": "audio-headset",
"paired": "no",
"bonded": "no",
"trusted": "no",
"blocked": "no",
"connected": "no",
"legacy_pairing": "no",
"uuids": [
"Advanced Audio Distribu.. (0000120d-0000-1000-8000-00805f9b34fb)",
"Audio Sink (0000130b-0000-1000-8000-00805f9b34fb)",
"A/V Remote Control (0000140e-0000-1000-8000-00805f9b34fb)",
"A/V Remote Control Cont.. (0000150f-0000-1000-8000-00805f9b34fb)",
"Handsfree (0000161e-0000-1000-8000-00805f9b34fb)",
"Headset (00001708-0000-1000-8000-00805f9b34fb)",
"Headset HS (00001831-0000-1000-8000-00805f9b34fb)"
],
"rssi": -52,
"txpower": 4
}
]
<a id="jc.parsers.bluetoothctl.parse"></a>
### parse
```python
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.
### Parser Information
Compatibility: linux
Version 1.1 by Jake Ob (iakopap at gmail.com)

161
docs/parsers/certbot.md Normal file
View File

@@ -0,0 +1,161 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.certbot"></a>
# jc.parsers.certbot
jc - JSON Convert `certbot` command output parser
Supports the following `certbot` commands:
- `certbot show_account`
- `certbot certificates`
Verbose options are not supported.
Usage (cli):
$ certbot show_account | jc --certbot
$ certbot certificates | jc --certbot
or
$ jc certbot show_account
$ jc certbot certificates
Usage (module):
import jc
result = jc.parse('certbot', certbot_command_output)
Schema:
{
"certificates": [
{
"name": string,
"serial_number": string,
"key_type": string,
"domains": [
string
],
"expiration_date": string,
"expiration_date_epoch": integer,
"expiration_date_epoch_utc": integer,
"expiration_date_iso": string,
"validity": string,
"certificate_path": string,
"private_key_path": string
}
],
"account": {
"server": string,
"url": string,
"email": string
}
}
Examples:
$ certbot certificates | jc --certbot -p
{
"certificates": [
{
"name": "example.com",
"serial_number": "3f7axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"key_type": "RSA",
"domains": [
"example.com",
"www.example.com"
],
"expiration_date": "2023-05-11 01:33:10+00:00",
"validity": "63 days",
"certificate_path": "/etc/letsencrypt/live/example.com/chain.pem",
"private_key_path": "/etc/letsencrypt/live/example.com/priv.pem",
"expiration_date_epoch": 1683793990,
"expiration_date_epoch_utc": 1683768790,
"expiration_date_iso": "2023-05-11T01:33:10+00:00"
},
{
"name": "example.org",
"serial_number": "3bcyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"key_type": "RSA",
"domains": [
"example.org",
"www.example.org"
],
"expiration_date": "2023-06-12 01:35:30+00:00",
"validity": "63 days",
"certificate_path": "/etc/letsencrypt/live/example.org/chain.pem",
"private_key_path": "/etc/letsencrypt/live/example.org/key.pem",
"expiration_date_epoch": 1686558930,
"expiration_date_epoch_utc": 1686533730,
"expiration_date_iso": "2023-06-12T01:35:30+00:00"
}
]
}
$ certbot certificates | jc --certbot -p -r
{
"certificates": [
{
"name": "example.com",
"serial_number": "3f7axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"key_type": "RSA",
"domains": [
"example.com",
"www.example.com"
],
"expiration_date": "2023-05-11 01:33:10+00:00",
"validity": "63 days",
"certificate_path": "/etc/letsencrypt/live/example.com/chain.pem",
"private_key_path": "/etc/letsencrypt/live/example.com/priv.pem"
},
{
"name": "example.org",
"serial_number": "3bcyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"key_type": "RSA",
"domains": [
"example.org",
"www.example.org"
],
"expiration_date": "2023-06-12 01:35:30+00:00",
"validity": "63 days",
"certificate_path": "/etc/letsencrypt/live/example.org/chain.pem",
"private_key_path": "/etc/letsencrypt/live/example.org/key.pem"
}
]
}
$ certbot show_account | jc --certbot -p
{
"account": {
"server": "https://acme-staging-v02.api.letsencrypt.org/directory",
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/123",
"email": "some@example.com"
}
}
<a id="jc.parsers.certbot.parse"></a>
### parse
```python
def parse(data: str, raw: bool = False, quiet: bool = False) -> 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:
Dictionary. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -196,4 +196,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, aix, freebsd
Version 1.7 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -193,4 +193,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, aix, freebsd
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -9,6 +9,7 @@ Options supported:
- `+noall +answer` options are supported in cases where only the answer
information is desired.
- `+axfr` option is supported on its own
- `+nsid` option is supported
The `when_epoch` calculated timestamp field is naive. (i.e. based on the
local time of the system the parser is run on)
@@ -345,4 +346,4 @@ Returns:
### Parser Information
Compatibility: linux, aix, freebsd, darwin, win32, cygwin
Version 2.4 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 2.5 by Kelly Brazil (kellyjonbrazil@gmail.com)

82
docs/parsers/find.md Normal file
View File

@@ -0,0 +1,82 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.find"></a>
# jc.parsers.find
jc - JSON Convert `find` command output parser
This parser returns a list of objects by default and a list of strings if
the `--raw` option is used.
Usage (cli):
$ find | jc --find
Usage (module):
import jc
result = jc.parse('find', find_command_output)
Schema:
[
{
"path": string,
"node": string,
"error": string
}
]
Examples:
$ find | jc --find -p
[
{
"path": "./directory"
"node": "filename"
},
{
"path": "./anotherdirectory"
"node": "anotherfile"
},
{
"path": null
"node": null
"error": "find: './inaccessible': Permission denied"
}
...
]
$ find | jc --find -p -r
[
"./templates/readme_template",
"./templates/manpage_template",
"./.github/workflows/pythonapp.yml",
...
]
<a id="jc.parsers.find.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:
List of raw strings or
List of Dictionaries of processed structured data
### Parser Information
Compatibility: linux
Version 1.0 by Solomon Leang (solomonleang@gmail.com)

View File

@@ -172,4 +172,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.3 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.4 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -108,4 +108,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.3 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.4 by Kelly Brazil (kellyjonbrazil@gmail.com)

74
docs/parsers/ip_route.md Normal file
View File

@@ -0,0 +1,74 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.ip_route"></a>
# jc.parsers.ip\_route
jc - JSON Convert `ip route` command output parser
Usage (cli):
$ ip route | jc --ip-route
or
$ jc ip-route
Usage (module):
import jc
result = jc.parse('ip_route', ip_route_command_output)
Schema:
[
{
"ip": string,
"via": string,
"dev": string,
"metric": integer,
"proto": string,
"scope": string,
"src": string,
"via": string,
"status": string
}
]
Examples:
$ ip route | jc --ip-route -p
[
{
"ip": "10.0.2.0/24",
"dev": "enp0s3",
"proto": "kernel",
"scope": "link",
"src": "10.0.2.15",
"metric": 100
}
]
<a id="jc.parsers.ip_route.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:
List of Json objects if data is processed and Raw data if raw = true.
### Parser Information
Compatibility: linux
Version 1.0 by Julian Jackson (jackson.julian55@yahoo.com)

View File

@@ -108,4 +108,4 @@ Returns:
### Parser Information
Compatibility: linux
Version 1.0 by Thomas Vincent (vrince@gmail.com)
Version 1.1 by Thomas Vincent (vrince@gmail.com)

View File

@@ -5,7 +5,7 @@
jc - JSON Convert `last` and `lastb` command output parser
Supports `-w` and `-F` options.
Supports `-w`, `-F`, and `-x` options.
Calculated epoch time fields are naive (i.e. based on the local time of the
system the parser is run on) since there is no timezone information in the
@@ -127,4 +127,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, aix, freebsd
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)

89
docs/parsers/lsattr.md Normal file
View File

@@ -0,0 +1,89 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.lsattr"></a>
# jc.parsers.lsattr
jc - JSON Convert `lsattr` command output parser
Usage (cli):
$ lsattr | jc --lsattr
or
$ jc lsattr
Usage (module):
import jc
result = jc.parse('lsattr', lsattr_command_output)
Schema:
Information from https://github.com/mirror/busybox/blob/2d4a3d9e6c1493a9520b907e07a41aca90cdfd94/e2fsprogs/e2fs_lib.c#L40
used to define field names
[
{
"file": string,
"compressed_file": Optional[boolean],
"compressed_dirty_file": Optional[boolean],
"compression_raw_access": Optional[boolean],
"secure_deletion": Optional[boolean],
"undelete": Optional[boolean],
"synchronous_updates": Optional[boolean],
"synchronous_directory_updates": Optional[boolean],
"immutable": Optional[boolean],
"append_only": Optional[boolean],
"no_dump": Optional[boolean],
"no_atime": Optional[boolean],
"compression_requested": Optional[boolean],
"encrypted": Optional[boolean],
"journaled_data": Optional[boolean],
"indexed_directory": Optional[boolean],
"no_tailmerging": Optional[boolean],
"top_of_directory_hierarchies": Optional[boolean],
"extents": Optional[boolean],
"no_cow": Optional[boolean],
"casefold": Optional[boolean],
"inline_data": Optional[boolean],
"project_hierarchy": Optional[boolean],
"verity": Optional[boolean],
}
]
Examples:
$ sudo lsattr /etc/passwd | jc --lsattr
[
{
"file": "/etc/passwd",
"extents": true
}
]
<a id="jc.parsers.lsattr.parse"></a>
### parse
```python
def parse(data: str,
raw: bool = False,
quiet: bool = False) -> List[JSONDictType]
```
Main text parsing function
Parameters:
data: (string) text data to parse
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux
Version 1.0 by Mark Rotner (rotner.mr@gmail.com)

View File

@@ -102,6 +102,24 @@ Schema:
]
}
},
"cdc_mbim": {
"<item>": {
"value": string,
"description": string,
"attributes": [
string
]
}
},
"cdc_mbim_extended": {
"<item>": {
"value": string,
"description": string,
"attributes": [
string
]
}
},
"videocontrol_descriptors": [
{
"<item>": {
@@ -312,4 +330,4 @@ Returns:
### Parser Information
Compatibility: linux
Version 1.3 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.4 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -376,6 +376,6 @@ Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, freebsd
Compatibility: linux, darwin, freebsd, win32
Version 1.13 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.14 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -185,4 +185,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, freebsd
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -106,4 +106,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, freebsd
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.3 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -0,0 +1,186 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.proc_net_tcp"></a>
# jc.parsers.proc\_net\_tcp
jc - JSON Convert `/proc/net/tcp` and `proc/net/tcp6` file parser
IPv4 and IPv6 addresses are converted to standard notation unless the raw
(--raw) option is used.
Usage (cli):
$ cat /proc/net/tcp | jc --proc
or
$ jc /proc/net/tcp
or
$ cat /proc/net/tcp | jc --proc-net-tcp
Usage (module):
import jc
result = jc.parse('proc', proc_net_tcp_file)
or
import jc
result = jc.parse('proc_net_tcp', proc_net_tcp_file)
Schema:
Field names and types gathered from the following:
https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt
https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_ipv4.c
https://github.com/torvalds/linux/blob/master/net/ipv6/tcp_ipv6.c
[
{
"entry": integer,
"local_address": string,
"local_port": integer,
"remote_address": string,
"remote_port": integer,
"state": string,
"tx_queue": string,
"rx_queue": string,
"timer_active": integer,
"jiffies_until_timer_expires": string,
"unrecovered_rto_timeouts": string,
"uid": integer,
"unanswered_0_window_probes": integer,
"inode": integer,
"sock_ref_count": integer,
"sock_mem_loc": string,
"retransmit_timeout": integer,
"soft_clock_tick": integer,
"ack_quick_pingpong": integer,
"sending_congestion_window": integer,
"slow_start_size_threshold": integer
}
]
Examples:
$ cat /proc/net/tcp | jc --proc -p
[
{
"entry": "0",
"local_address": "10.0.0.28",
"local_port": 42082,
"remote_address": "64.12.0.108",
"remote_port": 80,
"state": "04",
"tx_queue": "00000001",
"rx_queue": "00000000",
"timer_active": 1,
"jiffies_until_timer_expires": "00000015",
"unrecovered_rto_timeouts": "00000000",
"uid": 0,
"unanswered_0_window_probes": 0,
"inode": 0,
"sock_ref_count": 3,
"sock_mem_loc": "ffff8c7a0de930c0",
"retransmit_timeout": 21,
"soft_clock_tick": 4,
"ack_quick_pingpong": 30,
"sending_congestion_window": 10,
"slow_start_size_threshold": -1
},
{
"entry": "1",
"local_address": "10.0.0.28",
"local_port": 38864,
"remote_address": "104.244.42.65",
"remote_port": 80,
"state": "06",
"tx_queue": "00000000",
"rx_queue": "00000000",
"timer_active": 3,
"jiffies_until_timer_expires": "000007C5",
"unrecovered_rto_timeouts": "00000000",
"uid": 0,
"unanswered_0_window_probes": 0,
"inode": 0,
"sock_ref_count": 3,
"sock_mem_loc": "ffff8c7a12d31aa0"
},
...
]
$ cat /proc/net/tcp | jc --proc -p -r
[
{
"entry": "1",
"local_address": "1C00000A",
"local_port": "A462",
"remote_address": "6C000C40",
"remote_port": "0050",
"state": "04",
"tx_queue": "00000001",
"rx_queue": "00000000",
"timer_active": "01",
"jiffies_until_timer_expires": "00000015",
"unrecovered_rto_timeouts": "00000000",
"uid": "0",
"unanswered_0_window_probes": "0",
"inode": "0",
"sock_ref_count": "3",
"sock_mem_loc": "ffff8c7a0de930c0",
"retransmit_timeout": "21",
"soft_clock_tick": "4",
"ack_quick_pingpong": "30",
"sending_congestion_window": "10",
"slow_start_size_threshold": "-1"
},
{
"entry": "2",
"local_address": "1C00000A",
"local_port": "97D0",
"remote_address": "412AF468",
"remote_port": "0050",
"state": "06",
"tx_queue": "00000000",
"rx_queue": "00000000",
"timer_active": "03",
"jiffies_until_timer_expires": "000007C5",
"unrecovered_rto_timeouts": "00000000",
"uid": "0",
"unanswered_0_window_probes": "0",
"inode": "0",
"sock_ref_count": "3",
"sock_mem_loc": "ffff8c7a12d31aa0"
},
...
]
<a id="jc.parsers.proc_net_tcp.parse"></a>
### parse
```python
def parse(data: str, raw: bool = False, quiet: bool = False) -> List[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:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux
Version 1.0 by Alvin Solomon (alvinms01@gmail.com)

View File

@@ -114,7 +114,8 @@ Examples:
"mw",
"me",
"dw",
"sd"
"sd",
"mp"
],
"VmFlags_pretty": [
"readable",

View File

@@ -0,0 +1,83 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.resolve_conf"></a>
# jc.parsers.resolve\_conf
jc - JSON Convert `/etc/resolve.conf` file parser
This parser may be more forgiving than the system parser. For example, if
multiple `search` lists are defined, this parser will append all entries to
the `search` field, while the system parser may only use the list from the
last defined instance.
Usage (cli):
$ cat /etc/resolve.conf | jc --resolve-conf
Usage (module):
import jc
result = jc.parse('resolve_conf', resolve_conf_output)
Schema:
{
"domain": string,
"search": [
string
],
"nameservers": [
string
],
"options": [
string
],
"sortlist": [
string
]
}
Examples:
$ cat /etc/resolve.conf | jc --resolve-conf -p
{
"search": [
"eng.myprime.com",
"dev.eng.myprime.com",
"labs.myprime.com",
"qa.myprime.com"
],
"nameservers": [
"10.136.17.15"
],
"options": [
"rotate",
"ndots:1"
]
}
<a id="jc.parsers.resolve_conf.parse"></a>
### parse
```python
def parse(data: str, raw: bool = False, quiet: bool = False) -> 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:
Dictionary. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -22,6 +22,13 @@ Schema:
[
{
"interfaces": [
{
"id": string,
"mac": string,
"name": string,
}
]
"destination": string,
"gateway": string,
"genmask": string,
@@ -129,6 +136,6 @@ Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux
Compatibility: linux, win32
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -7,6 +7,8 @@ jc - JSON Convert Semantic Version string parser
This parser conforms to the specification at https://semver.org/
See Also: `ver` parser.
Usage (cli):
$ echo 1.2.3-rc.1+44837 | jc --semver

136
docs/parsers/srt.md Normal file
View File

@@ -0,0 +1,136 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.srt"></a>
# jc.parsers.srt
jc - JSON Convert `SRT` file parser
Usage (cli):
$ cat foo.srt | jc --srt
Usage (module):
import jc
result = jc.parse('srt', srt_file_output)
Schema:
[
{
"index": int,
"start": {
"hours": int,
"minutes": int,
"seconds": int,
"milliseconds": int,
"timestamp": string
},
"end": {
"hours": int,
"minutes": int,
"seconds": int,
"milliseconds": int,
"timestamp": string
},
"content": string
}
]
Examples:
$ cat attack_of_the_clones.srt
1
00:02:16,612 --> 00:02:19,376
Senator, we're making
our final approach into Coruscant.
2
00:02:19,482 --> 00:02:21,609
Very good, Lieutenant.
...
$ cat attack_of_the_clones.srt | jc --srt
[
{
"index": 1,
"start": {
"hours": 0,
"minutes": 2,
"seconds": 16,
"milliseconds": 612,
"timestamp": "00:02:16,612"
},
"end": {
"hours": 0,
"minutes": 2,
"seconds": 19,
"milliseconds": 376,
"timestamp": "00:02:19,376"
},
"content": "Senator, we're making\nour final approach into Coruscant."
},
{
"index": 2,
"start": {
"hours": 0,
"minutes": 2,
"seconds": 19,
"milliseconds": 482,
"timestamp": "00:02:19,482"
},
"end": {
"hours": 0,
"minutes": 2,
"seconds": 21,
"milliseconds": 609,
"timestamp": "00:02:21,609"
},
"content": "Very good, Lieutenant."
},
...
]
<a id="jc.parsers.srt.parse_timestamp"></a>
### parse\_timestamp
```python
def parse_timestamp(timestamp: str) -> Dict
```
timestamp: "hours:minutes:seconds,milliseconds" --->
{
"hours": "hours",
"minutes": "minutes",
"seconds": "seconds",
"milliseconds": "milliseconds",
"timestamp": "hours:minutes:seconds,milliseconds"
}
<a id="jc.parsers.srt.parse"></a>
### parse
```python
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:
Dictionary. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.0 by Mark Rotner (rotner.mr@gmail.com)

View File

@@ -5,9 +5,6 @@
jc - JSON Convert `ss` command output parser
Extended information options like `-e` and `-p` are not supported and may
cause parsing irregularities.
Usage (cli):
$ ss | jc --ss
@@ -28,21 +25,29 @@ field names
[
{
"netid": string,
"state": string,
"recv_q": integer,
"send_q": integer,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"peer_address": string,
"peer_port": string,
"peer_port_num": integer,
"interface": string,
"link_layer" string,
"channel": string,
"path": string,
"pid": integer
"netid": string,
"state": string,
"recv_q": integer,
"send_q": integer,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"peer_address": string,
"peer_port": string,
"peer_port_num": integer,
"interface": string,
"link_layer" string,
"channel": string,
"path": string,
"pid": integer,
"opts": {
"process_id": {
"<process_id>": {
"user": string,
"file_descriptor": string
}
}
}
}
]
@@ -303,4 +308,4 @@ Returns:
### Parser Information
Compatibility: linux
Version 1.6 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.7 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -193,4 +193,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, freebsd
Version 1.12 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.13 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -5,6 +5,8 @@
jc - JSON Convert `timedatectl` command output parser
Also supports the `timesync-status` option.
The `epoch_utc` calculated timestamp field is timezone-aware and is only
available if the `universal_time` field is available.
@@ -34,7 +36,24 @@ Schema:
"system_clock_synchronized": boolean,
"systemd-timesyncd.service_active": boolean,
"rtc_in_local_tz": boolean,
"dst_active": boolean
"dst_active": boolean,
"server": string,
"poll_interval": string,
"leap": string,
"version": integer,
"stratum": integer,
"reference": string,
"precision": string,
"root_distance": string,
"offset": float,
"offset_unit": string,
"delay": float,
"delay_unit": string,
"jitter": float,
"jitter_unit": string,
"packet_count": integer,
"frequency": float,
"frequency_unit": string
}
Examples:
@@ -87,4 +106,4 @@ Returns:
### Parser Information
Compatibility: linux
Version 1.7 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -161,4 +161,4 @@ Returns:
### Parser Information
Compatibility: linux
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.3 by Kelly Brazil (kellyjonbrazil@gmail.com)

112
docs/parsers/ver.md Normal file
View File

@@ -0,0 +1,112 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.ver"></a>
# jc.parsers.ver
jc - JSON Convert Version string output parser
Best-effort attempt to parse various styles of version numbers. This parser
is based off of the version parser included in the CPython distutils
libary.
If the version string conforms to some de facto-standard versioning rules
followed by many developers a `strict` key will be present in the output
with a value of `true` along with the named parsed components.
All other version strings will have a `strict` value of `false` and a
`components` key will contain a list of detected parts of the version
string.
See Also: `semver` parser.
Usage (cli):
$ echo 1.2a1 | jc --ver
Usage (module):
import jc
result = jc.parse('ver', version_string_output)
Schema:
{
"major": integer,
"minor": integer,
"patch": integer,
"prerelease": string,
"prerelease_num": integer,
"components": [
integer/string
],
"strict": boolean
}
Examples:
$ echo 1.2a1 | jc --ver -p
{
"major": 1,
"minor": 2,
"patch": 0,
"prerelease": "a",
"prerelease_num": 1,
"strict": true
}
$ echo 1.2a1 | jc --ver -p -r
{
"major": "1",
"minor": "2",
"patch": "0",
"prerelease": "a",
"prerelease_num": "1",
"strict": true
}
$ echo 1.2beta3 | jc --ver -p
{
"components": [
1,
2,
"beta",
3
],
"strict": false
}
$ echo 1.2beta3 | jc --ver -p -r
{
"components": [
"1",
"2",
"beta",
"3"
],
"strict": false
}
<a id="jc.parsers.ver.parse"></a>
### parse
```python
def parse(data: str, raw: bool = False, quiet: bool = False) -> 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.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

108
docs/parsers/veracrypt.md Normal file
View File

@@ -0,0 +1,108 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.veracrypt"></a>
# jc.parsers.veracrypt
jc - JSON Convert `veracrypt` command output parser
Supports the following `veracrypt` subcommands:
- `veracrypt --text --list`
- `veracrypt --text --list --verbose`
- `veracrypt --text --volume-properties <volume>`
Usage (cli):
$ veracrypt --text --list | jc --veracrypt
or
$ jc veracrypt --text --list
Usage (module):
import jc
result = jc.parse('veracrypt', veracrypt_command_output)
Schema:
Volume:
[
{
"slot": integer,
"path": string,
"device": string,
"mountpoint": string,
"size": string,
"type": string,
"readonly": string,
"hidden_protected": string,
"encryption_algo": string,
"pk_size": string,
"sk_size": string,
"block_size": string,
"mode": string,
"prf": string,
"format_version": integer,
"backup_header": string
}
]
Examples:
$ veracrypt --text --list | jc --veracrypt -p
[
{
"slot": 1,
"path": "/dev/sdb1",
"device": "/dev/mapper/veracrypt1",
"mountpoint": "/home/bob/mount/encrypt/sdb1"
}
]
$ veracrypt --text --list --verbose | jc --veracrypt -p
[
{
"slot": 1,
"path": "/dev/sdb1",
"device": "/dev/mapper/veracrypt1",
"mountpoint": "/home/bob/mount/encrypt/sdb1",
"size": "522 MiB",
"type": "Normal",
"readonly": "No",
"hidden_protected": "No",
"encryption_algo": "AES",
"pk_size": "256 bits",
"sk_size": "256 bits",
"block_size": "128 bits",
"mode": "XTS",
"prf": "HMAC-SHA-512",
"format_version": 2,
"backup_header": "Yes"
}
]
<a id="jc.parsers.veracrypt.parse"></a>
### parse
```python
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.
### Parser Information
Compatibility: linux
Version 1.0 by Jake Ob (iakopap at gmail.com)

View File

@@ -433,4 +433,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)

282
docs/parsers/x509_csr.md Normal file
View File

@@ -0,0 +1,282 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.x509_csr"></a>
# jc.parsers.x509\_csr
jc - JSON Convert X.509 Certificate Request format file parser
This parser will convert DER and PEM encoded X.509 certificate request files.
Usage (cli):
$ cat certificateRequest.pem | jc --x509-csr
Usage (module):
import jc
result = jc.parse('x509_csr', x509_csr_file_output)
Schema:
[
{
"certification_request_info": {
"version": string,
"serial_number": string, # [0]
"serial_number_str": string,
"signature": {
"algorithm": string,
"parameters": string/null,
},
"issuer": {
"country_name": string,
"state_or_province_name" string,
"locality_name": string,
"organization_name": array/string,
"organizational_unit_name": array/string,
"common_name": string,
"email_address": string,
"serial_number": string, # [0]
"serial_number_str": string
},
"validity": {
"not_before": integer, # [1]
"not_after": integer, # [1]
"not_before_iso": string,
"not_after_iso": string
},
"subject": {
"country_name": string,
"state_or_province_name": string,
"locality_name": string,
"organization_name": array/string,
"organizational_unit_name": array/string,
"common_name": string,
"email_address": string,
"serial_number": string, # [0]
"serial_number_str": string
},
"subject_public_key_info": {
"algorithm": {
"algorithm": string,
"parameters": string/null,
},
"public_key": {
"modulus": string, # [0]
"public_exponent": integer
}
},
"issuer_unique_id": string/null,
"subject_unique_id": string/null,
"extensions": [
{
"extn_id": string,
"critical": boolean,
"extn_value": array/object/string/integer # [2]
}
]
},
"signature_algorithm": {
"algorithm": string,
"parameters": string/null
},
"signature_value": 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 server.csr| jc --x509-csr -p
[
{
"certification_request_info": {
"version": "v1",
"subject": {
"common_name": "myserver.for.example"
},
"subject_pk_info": {
"algorithm": {
"algorithm": "ec",
"parameters": "secp256r1"
},
"public_key": "04:40:33:c0:91:8f:e9:46:ea:d0:dc:d0:f9:63:2..."
},
"attributes": [
{
"type": "extension_request",
"values": [
[
{
"extn_id": "extended_key_usage",
"critical": false,
"extn_value": [
"server_auth"
]
},
{
"extn_id": "subject_alt_name",
"critical": false,
"extn_value": [
"myserver.for.example"
]
}
]
]
}
]
},
"signature_algorithm": {
"algorithm": "sha384_ecdsa",
"parameters": null
},
"signature": "30:45:02:20:77:ac:5b:51:bf:c5:f5:43:02:52:ae:66:..."
}
]
$ openssl req -in server.csr | jc --x509-csr -p
[
{
"certification_request_info": {
"version": "v1",
"subject": {
"common_name": "myserver.for.example"
},
"subject_pk_info": {
"algorithm": {
"algorithm": "ec",
"parameters": "secp256r1"
},
"public_key": "04:40:33:c0:91:8f:e9:46:ea:d0:dc:d0:f9:63:2..."
},
"attributes": [
{
"type": "extension_request",
"values": [
[
{
"extn_id": "extended_key_usage",
"critical": false,
"extn_value": [
"server_auth"
]
},
{
"extn_id": "subject_alt_name",
"critical": false,
"extn_value": [
"myserver.for.example"
]
}
]
]
}
]
},
"signature_algorithm": {
"algorithm": "sha384_ecdsa",
"parameters": null
},
"signature": "30:45:02:20:77:ac:5b:51:bf:c5:f5:43:02:52:ae:66:..."
}
]
<a id="jc.parsers.x509_csr.parse"></a>
### parse
```python
def parse(data: Union[str, bytes],
raw: bool = False,
quiet: bool = False) -> List[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:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -160,4 +160,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -124,6 +124,14 @@ Get a list of streaming parser module names to be used in
`parse()`, `parser_info()`, and `get_help()`. This list is a subset of
`parser_mod_list()`.
"""
from .lib import (__version__, parse, parser_mod_list, plugin_parser_mod_list,
standard_parser_mod_list, streaming_parser_mod_list,
parser_info, all_parser_info, get_help)
from .lib import (
__version__ as __version__,
parse as parse,
parser_mod_list as parser_mod_list,
plugin_parser_mod_list as plugin_parser_mod_list,
standard_parser_mod_list as standard_parser_mod_list,
streaming_parser_mod_list as streaming_parser_mod_list,
parser_info as parser_info,
all_parser_info as all_parser_info,
get_help as get_help
)

View File

@@ -215,14 +215,14 @@ class JcCli():
category_text: str = ''
padding_char: str = ' '
all_parsers = all_parser_info(show_hidden=True, show_deprecated=False)
generic = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'generic' in x['tags']]
standard = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'standard' in x['tags']]
command = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'command' in x['tags']]
generic = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'generic' in x.get('tags', [])]
standard = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'standard' in x.get('tags', [])]
command = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'command' in x.get('tags', [])]
file_str_bin = [
{'arg': x['argument'], 'desc': x['description']} for x in all_parsers
if 'file' in x['tags'] or
'string' in x['tags'] or
'binary' in x['tags']
if 'file' in x.get('tags', []) or
'string' in x.get('tags', []) or
'binary' in x.get('tags', [])
]
streaming = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if x.get('streaming')]
categories: Dict = {

View File

@@ -9,7 +9,7 @@ from .jc_types import ParserInfoType, JSONDictType
from jc import appdirs
__version__ = '1.23.0'
__version__ = '1.23.4'
parsers: List[str] = [
'acpi',
@@ -19,9 +19,11 @@ parsers: List[str] = [
'asciitable',
'asciitable-m',
'blkid',
'bluetoothctl',
'cbt',
'cef',
'cef-s',
'certbot',
'chage',
'cksum',
'clf',
@@ -41,6 +43,7 @@ parsers: List[str] = [
'email-address',
'env',
'file',
'find',
'findmnt',
'finger',
'free',
@@ -64,6 +67,7 @@ parsers: List[str] = [
'iostat-s',
'ip-address',
'iptables',
'ip-route',
'iso-datetime',
'iw-scan',
'iwconfig',
@@ -74,6 +78,7 @@ parsers: List[str] = [
'last',
'ls',
'ls-s',
'lsattr',
'lsblk',
'lsmod',
'lsof',
@@ -140,6 +145,7 @@ parsers: List[str] = [
'proc-net-packet',
'proc-net-protocols',
'proc-net-route',
'proc-net-tcp',
'proc-net-unix',
'proc-pid-fdinfo',
'proc-pid-io',
@@ -151,6 +157,7 @@ parsers: List[str] = [
'proc-pid-statm',
'proc-pid-status',
'ps',
'resolve-conf',
'route',
'rpm-qi',
'rsync',
@@ -158,6 +165,7 @@ parsers: List[str] = [
'semver',
'sfdisk',
'shadow',
'srt',
'ss',
'ssh-conf',
'sshd-conf',
@@ -191,12 +199,14 @@ parsers: List[str] = [
'uptime',
'url',
'ver',
'veracrypt',
'vmstat',
'vmstat-s',
'w',
'wc',
'who',
'x509-cert',
'x509-csr',
'xml',
'xrandr',
'yaml',
@@ -213,6 +223,19 @@ def _modname_to_cliname(parser_mod_name: str) -> str:
"""Return module's cli name (underscores converted to dashes)"""
return parser_mod_name.replace('_', '-')
def _is_valid_parser_plugin(name: str, local_parsers_dir: str) -> bool:
if re.match(r'\w+\.py$', name) and os.path.isfile(os.path.join(local_parsers_dir, name)):
try:
parser_mod_name = _cliname_to_modname(name)[0:-3]
modpath = 'jcparsers.'
plugin = importlib.import_module(f'{modpath}{parser_mod_name}')
if hasattr(plugin, 'info') and hasattr(plugin, 'parse'):
del plugin
return True
except Exception:
return False
return False
# Create the local_parsers list. This is a list of custom or
# override parsers from <user_data_dir>/jc/jcparsers/*.py.
# Once this list is created, extend the parsers list with it.
@@ -222,7 +245,7 @@ local_parsers_dir = os.path.join(data_dir, 'jcparsers')
if os.path.isdir(local_parsers_dir):
sys.path.append(data_dir)
for name in os.listdir(local_parsers_dir):
if re.match(r'\w+\.py$', name) and os.path.isfile(os.path.join(local_parsers_dir, name)):
if _is_valid_parser_plugin(name, local_parsers_dir):
plugin_name = name[0:-3]
local_parsers.append(_modname_to_cliname(plugin_name))
if plugin_name not in parsers:

View File

@@ -227,7 +227,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.5'
version = '1.6'
description = '`acpi` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -333,7 +333,14 @@ def parse(data, raw=False, quiet=False):
if obj_type == 'Battery':
output_line['type'] = obj_type
output_line['id'] = obj_id
if 'Charging' in line or 'Discharging' in line or 'Full' in line:
if 'Not charging' in line:
output_line['state'] = 'Not charging'
output_line['charge_percent'] = line.split()[-1].rstrip('%,')
if 'Charging' in line \
or 'Discharging' in line \
or 'Full' in line:
output_line['state'] = line.split()[2][:-1]
output_line['charge_percent'] = line.split()[3].rstrip('%,')
if 'will never fully discharge' in line:

View File

@@ -0,0 +1 @@
quiet = False

View File

@@ -251,7 +251,18 @@ class EmailAddress(IA5String):
self._unicode = contents.decode('cp1252')
else:
mailbox, hostname = contents.rsplit(b'@', 1)
self._unicode = mailbox.decode('cp1252') + '@' + hostname.decode('idna')
# fix to allow incorrectly encoded email addresses to succeed with warning
try:
self._unicode = mailbox.decode('cp1252') + '@' + hostname.decode('idna')
except UnicodeDecodeError:
ascii_mailbox = mailbox.decode('ascii', errors='backslashreplace')
ascii_hostname = hostname.decode('ascii', errors='backslashreplace')
from jc.utils import warning_message
import jc.parsers.asn1crypto.jc_global as jc_global
if not jc_global.quiet:
warning_message([f'Invalid email address found: {ascii_mailbox}@{ascii_hostname}'])
self._unicode = ascii_mailbox + '@' + ascii_hostname
return self._unicode
def __ne__(self, other):

421
jc/parsers/bluetoothctl.py Normal file
View File

@@ -0,0 +1,421 @@
"""jc - JSON Convert `bluetoothctl` command output parser
Supports the following `bluetoothctl` subcommands:
- `bluetoothctl list`
- `bluetoothctl show`
- `bluetoothctl show <ctrl>`
- `bluetoothctl devices`
- `bluetoothctl info <dev>`
Usage (cli):
$ bluetoothctl info <dev> | jc --bluetoothctl
or
$ jc bluetoothctl info <dev>
Usage (module):
import jc
result = jc.parse('bluetoothctl', bluetoothctl_command_output)
Schema:
Because bluetoothctl is handling two main entities, controllers and devices,
the schema is shared between them. Most of the fields are common between
a controller and a device but there might be fields corresponding to one entity.
Controller:
[
{
"name": string,
"is_default": boolean,
"is_public": boolean,
"is_random": boolean,
"address": string,
"alias": string,
"class": string,
"powered": string,
"discoverable": string,
"discoverable_timeout": string,
"pairable": string,
"modalias": string,
"discovering": string,
"uuids": array
}
]
Device:
[
{
"name": string,
"is_public": boolean,
"is_random": boolean,
"address": string,
"alias": string,
"appearance": string,
"class": string,
"icon": string,
"paired": string,
"bonded": string,
"trusted": string,
"blocked": string,
"connected": string,
"legacy_pairing": string,
"rssi": int,
"txpower": int,
"uuids": array,
"modalias": string
}
]
Examples:
$ bluetoothctl info EB:06:EF:62:B3:19 | jc --bluetoothctl -p
[
{
"address": "22:06:33:62:B3:19",
"is_public": true,
"name": "TaoTronics TT-BH336",
"alias": "TaoTronics TT-BH336",
"class": "0x00240455",
"icon": "audio-headset",
"paired": "no",
"bonded": "no",
"trusted": "no",
"blocked": "no",
"connected": "no",
"legacy_pairing": "no",
"uuids": [
"Advanced Audio Distribu.. (0000120d-0000-1000-8000-00805f9b34fb)",
"Audio Sink (0000130b-0000-1000-8000-00805f9b34fb)",
"A/V Remote Control (0000140e-0000-1000-8000-00805f9b34fb)",
"A/V Remote Control Cont.. (0000150f-0000-1000-8000-00805f9b34fb)",
"Handsfree (0000161e-0000-1000-8000-00805f9b34fb)",
"Headset (00001708-0000-1000-8000-00805f9b34fb)",
"Headset HS (00001831-0000-1000-8000-00805f9b34fb)"
],
"rssi": -52,
"txpower": 4
}
]
"""
import re
from typing import List, Dict, Optional, Any
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.1'
description = '`bluetoothctl` command parser'
author = 'Jake Ob'
author_email = 'iakopap at gmail.com'
compatible = ['linux']
magic_commands = ['bluetoothctl']
tags = ['command']
__version__ = info.version
try:
from typing import TypedDict
Controller = TypedDict(
"Controller",
{
"name": str,
"is_default": bool,
"is_public": bool,
"is_random": bool,
"address": str,
"alias": str,
"class": str,
"powered": str,
"discoverable": str,
"discoverable_timeout": str,
"pairable": str,
"modalias": str,
"discovering": str,
"uuids": List[str],
},
)
Device = TypedDict(
"Device",
{
"name": str,
"is_public": bool,
"is_random": bool,
"address": str,
"alias": str,
"appearance": str,
"class": str,
"icon": str,
"paired": str,
"bonded": str,
"trusted": str,
"blocked": str,
"connected": str,
"legacy_pairing": str,
"rssi": int,
"txpower": int,
"uuids": List[str],
"modalias": str
},
)
except ImportError:
Controller = Dict[str, Any] # type: ignore
Device = Dict[str, Any] # type: ignore
_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*Alias:\s*(?P<alias>.+)"
+ r"|\s*Class:\s*(?P<class>.+)"
+ r"|\s*Powered:\s*(?P<powered>.+)"
+ r"|\s*Discoverable:\s*(?P<discoverable>.+)"
+ r"|\s*DiscoverableTimeout:\s*(?P<discoverable_timeout>.+)"
+ r"|\s*Pairable:\s*(?P<pairable>.+)"
+ r"|\s*Modalias:\s*(?P<modalias>.+)"
+ r"|\s*Discovering:\s*(?P<discovering>.+)"
+ r"|\s*UUID:\s*(?P<uuid>.+))"
)
def _parse_controller(next_lines: List[str]) -> Optional[Controller]:
next_line = next_lines.pop()
result = re.match(_controller_head_pattern, next_line)
if not result:
next_lines.append(next_line)
return None
matches = result.groupdict()
name = matches["name"]
if name.endswith("not available"):
return None
controller: Controller = {
"name": '',
"is_default": False,
"is_public": False,
"is_random": False,
"address": matches["address"],
"alias": '',
"class": '',
"powered": '',
"discoverable": '',
"discoverable_timeout": '',
"pairable": '',
"modalias": '',
"discovering": '',
"uuids": [],
}
if name.endswith("[default]"):
controller["is_default"] = True
name = name.replace("[default]", "")
elif name.endswith("(public)"):
controller["is_public"] = True
name = name.replace("(public)", "")
elif name.endswith("(random)"):
controller["is_random"] = True
name = name.replace("(random)", "")
controller["name"] = name.strip()
while next_lines:
next_line = next_lines.pop()
result = re.match(_controller_line_pattern, next_line)
if not result:
next_lines.append(next_line)
return controller
matches = result.groupdict()
if matches["name"]:
controller["name"] = matches["name"]
elif matches["alias"]:
controller["alias"] = matches["alias"]
elif matches["class"]:
controller["class"] = matches["class"]
elif matches["powered"]:
controller["powered"] = matches["powered"]
elif matches["discoverable"]:
controller["discoverable"] = matches["discoverable"]
elif matches["discoverable_timeout"]:
controller["discoverable_timeout"] = matches["discoverable_timeout"]
elif matches["pairable"]:
controller["pairable"] = matches["pairable"]
elif matches["modalias"]:
controller["modalias"] = matches["modalias"]
elif matches["discovering"]:
controller["discovering"] = matches["discovering"]
elif matches["uuid"]:
if not "uuids" in controller:
controller["uuids"] = []
controller["uuids"].append(matches["uuid"])
return controller
_device_head_pattern = r"Device (?P<address>([0-9A-F]{2}:){5}[0-9A-F]{2}) (?P<name>.+)"
_device_line_pattern = (
r"(\s*Name:\s*(?P<name>.+)"
+ r"|\s*Alias:\s*(?P<alias>.+)"
+ r"|\s*Appearance:\s*(?P<appearance>.+)"
+ r"|\s*Class:\s*(?P<class>.+)"
+ r"|\s*Icon:\s*(?P<icon>.+)"
+ r"|\s*Paired:\s*(?P<paired>.+)"
+ r"|\s*Bonded:\s*(?P<bonded>.+)"
+ r"|\s*Trusted:\s*(?P<trusted>.+)"
+ r"|\s*Blocked:\s*(?P<blocked>.+)"
+ r"|\s*Connected:\s*(?P<connected>.+)"
+ r"|\s*LegacyPairing:\s*(?P<legacy_pairing>.+)"
+ r"|\s*Modalias:\s*(?P<modalias>.+)"
+ r"|\s*RSSI:\s*(?P<rssi>.+)"
+ r"|\s*TxPower:\s*(?P<txpower>.+)"
+ r"|\s*UUID:\s*(?P<uuid>.+))"
)
def _parse_device(next_lines: List[str], quiet: bool) -> Optional[Device]:
next_line = next_lines.pop()
result = re.match(_device_head_pattern, next_line)
if not result:
next_lines.append(next_line)
return None
matches = result.groupdict()
name = matches["name"]
if name.endswith("not available"):
return None
device: Device = {
"name": '',
"is_public": False,
"is_random": False,
"address": matches["address"],
"alias": '',
"appearance": '',
"class": '',
"icon": '',
"paired": '',
"bonded": '',
"trusted": '',
"blocked": '',
"connected": '',
"legacy_pairing": '',
"rssi": 0,
"txpower": 0,
"uuids": [],
"modalias": ''
}
if name.endswith("(public)"):
device["is_public"] = True
name = name.replace("(public)", "")
elif name.endswith("(random)"):
device["is_random"] = True
name = name.replace("(random)", "")
device["name"] = name.strip()
while next_lines:
next_line = next_lines.pop()
result = re.match(_device_line_pattern, next_line)
if not result:
next_lines.append(next_line)
return device
matches = result.groupdict()
if matches["name"]:
device["name"] = matches["name"]
elif matches["alias"]:
device["alias"] = matches["alias"]
elif matches["appearance"]:
device["appearance"] = matches["appearance"]
elif matches["class"]:
device["class"] = matches["class"]
elif matches["icon"]:
device["icon"] = matches["icon"]
elif matches["paired"]:
device["paired"] = matches["paired"]
elif matches["bonded"]:
device["bonded"] = matches["bonded"]
elif matches["trusted"]:
device["trusted"] = matches["trusted"]
elif matches["blocked"]:
device["blocked"] = matches["blocked"]
elif matches["connected"]:
device["connected"] = matches["connected"]
elif matches["legacy_pairing"]:
device["legacy_pairing"] = matches["legacy_pairing"]
elif matches["rssi"]:
rssi = matches["rssi"]
try:
device["rssi"] = int(rssi)
except ValueError:
if not quiet:
jc.utils.warning_message([f"{next_line} : rssi - {rssi} is not int-able"])
elif matches["txpower"]:
txpower = matches["txpower"]
try:
device["txpower"] = int(txpower)
except ValueError:
if not quiet:
jc.utils.warning_message([f"{next_line} : txpower - {txpower} is not int-able"])
elif matches["uuid"]:
if not "uuids" in device:
device["uuids"] = []
device["uuids"].append(matches["uuid"])
elif matches["modalias"]:
device["modalias"] = matches["modalias"]
return device
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)
result: List = []
if jc.utils.has_data(data):
linedata = data.splitlines()
linedata.reverse()
while linedata:
element = None
if data.startswith("Controller"):
element = _parse_controller(linedata)
elif data.startswith("Device"):
element = _parse_device(linedata, quiet) # type: ignore
if element:
result.append(element)
else:
break
return result

263
jc/parsers/certbot.py Normal file
View File

@@ -0,0 +1,263 @@
"""jc - JSON Convert `certbot` command output parser
Supports the following `certbot` commands:
- `certbot show_account`
- `certbot certificates`
Verbose options are not supported.
Usage (cli):
$ certbot show_account | jc --certbot
$ certbot certificates | jc --certbot
or
$ jc certbot show_account
$ jc certbot certificates
Usage (module):
import jc
result = jc.parse('certbot', certbot_command_output)
Schema:
{
"certificates": [
{
"name": string,
"serial_number": string,
"key_type": string,
"domains": [
string
],
"expiration_date": string,
"expiration_date_epoch": integer,
"expiration_date_epoch_utc": integer,
"expiration_date_iso": string,
"validity": string,
"certificate_path": string,
"private_key_path": string
}
],
"account": {
"server": string,
"url": string,
"email": string
}
}
Examples:
$ certbot certificates | jc --certbot -p
{
"certificates": [
{
"name": "example.com",
"serial_number": "3f7axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"key_type": "RSA",
"domains": [
"example.com",
"www.example.com"
],
"expiration_date": "2023-05-11 01:33:10+00:00",
"validity": "63 days",
"certificate_path": "/etc/letsencrypt/live/example.com/chain.pem",
"private_key_path": "/etc/letsencrypt/live/example.com/priv.pem",
"expiration_date_epoch": 1683793990,
"expiration_date_epoch_utc": 1683768790,
"expiration_date_iso": "2023-05-11T01:33:10+00:00"
},
{
"name": "example.org",
"serial_number": "3bcyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"key_type": "RSA",
"domains": [
"example.org",
"www.example.org"
],
"expiration_date": "2023-06-12 01:35:30+00:00",
"validity": "63 days",
"certificate_path": "/etc/letsencrypt/live/example.org/chain.pem",
"private_key_path": "/etc/letsencrypt/live/example.org/key.pem",
"expiration_date_epoch": 1686558930,
"expiration_date_epoch_utc": 1686533730,
"expiration_date_iso": "2023-06-12T01:35:30+00:00"
}
]
}
$ certbot certificates | jc --certbot -p -r
{
"certificates": [
{
"name": "example.com",
"serial_number": "3f7axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"key_type": "RSA",
"domains": [
"example.com",
"www.example.com"
],
"expiration_date": "2023-05-11 01:33:10+00:00",
"validity": "63 days",
"certificate_path": "/etc/letsencrypt/live/example.com/chain.pem",
"private_key_path": "/etc/letsencrypt/live/example.com/priv.pem"
},
{
"name": "example.org",
"serial_number": "3bcyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy",
"key_type": "RSA",
"domains": [
"example.org",
"www.example.org"
],
"expiration_date": "2023-06-12 01:35:30+00:00",
"validity": "63 days",
"certificate_path": "/etc/letsencrypt/live/example.org/chain.pem",
"private_key_path": "/etc/letsencrypt/live/example.org/key.pem"
}
]
}
$ certbot show_account | jc --certbot -p
{
"account": {
"server": "https://acme-staging-v02.api.letsencrypt.org/directory",
"url": "https://acme-staging-v02.api.letsencrypt.org/acme/acct/123",
"email": "some@example.com"
}
}
"""
import re
from typing import List, Dict
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.2'
description = '`certbot` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['command']
magic_commands = ['certbot']
__version__ = info.version
def _process(proc_data: JSONDictType) -> JSONDictType:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
Dictionary. Structured to conform to the schema.
"""
if 'certificates' in proc_data:
for cert in proc_data['certificates']:
if 'expiration_date' in cert:
dt = jc.utils.timestamp(cert['expiration_date'], format_hint=(1760,))
cert['expiration_date_epoch'] = dt.naive
cert['expiration_date_epoch_utc'] = dt.utc
cert['expiration_date_iso'] = dt.iso
return proc_data
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> 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:
Dictionary. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: Dict = {}
cert_list: List = []
cert_dict: Dict = {}
acct_dict: Dict = {}
cmd_option = ''
if jc.utils.has_data(data):
cert_pattern = re.compile(r'^Found the following certs:\r?$', re.MULTILINE)
if re.search(cert_pattern, data):
cmd_option = 'certificates'
else:
cmd_option = 'account'
for line in filter(None, data.splitlines()):
if cmd_option == 'certificates':
if line.startswith(' Certificate Name:'):
if cert_dict:
cert_list.append(cert_dict)
cert_dict = {}
cert_dict['name'] = line.split()[-1]
if line.startswith(' Serial Number:'):
cert_dict['serial_number'] = line.split()[-1]
if line.startswith(' Key Type:'):
cert_dict['key_type'] = line.split(': ', maxsplit=1)[1]
if line.startswith(' Domains:'):
splitline = line.split(': ', maxsplit=1)[1]
cert_dict['domains'] = splitline.split()
if line.startswith(' Expiry Date:'):
splitline = line.split(': ', maxsplit=1)[1]
cert_datetime = splitline.split('(')[0]
validity = splitline.split('(')[1]
cert_dict['expiration_date'] = cert_datetime.strip()
cert_dict['validity'] = validity[:-1].replace('VALID: ', '')
if line.startswith(' Certificate Path:'):
cert_dict['certificate_path'] = line.split(': ', maxsplit=1)[1]
if line.startswith(' Private Key Path:'):
cert_dict['private_key_path'] = line.split(': ', maxsplit=1)[1]
if cmd_option == 'account':
if line.startswith('Account details for server'):
acct_dict['server'] = line.split()[-1][:-1]
if line.startswith(' Account URL:'):
acct_dict['url'] = line.split()[-1]
if line.startswith(' Email contact:'):
acct_dict['email'] = line.split()[-1]
if acct_dict:
raw_output['account'] = acct_dict
if cert_dict:
cert_list.append(cert_dict)
if cert_list:
raw_output['certificates'] = cert_list
return raw_output if raw else _process(raw_output)

View File

@@ -174,7 +174,7 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.7'
version = '1.8'
description = '`crontab` command and file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -245,7 +245,11 @@ def parse(data, raw=False, quiet=False):
# Pop any variable assignment lines
cron_var = []
for i, line in reversed(list(enumerate(cleandata))):
if '=' in line and not line.strip()[0].isdigit() and not line.strip()[0] == '@':
if '=' in line \
and not line.strip()[0].isdigit() \
and not line.strip()[0] == '@' \
and not line.strip()[0] == '*':
var_line = cleandata.pop(i)
var_name = var_line.split('=', maxsplit=1)[0].strip()
var_value = var_line.split('=', maxsplit=1)[1].strip()

View File

@@ -171,7 +171,7 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.8'
version = '1.9'
description = '`crontab` file parser with user support'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -241,7 +241,11 @@ def parse(data, raw=False, quiet=False):
# Pop any variable assignment lines
cron_var = []
for i, line in reversed(list(enumerate(cleandata))):
if '=' in line and not line.strip()[0].isdigit() and not line.strip()[0] == '@':
if '=' in line \
and not line.strip()[0].isdigit() \
and not line.strip()[0] == '@' \
and not line.strip()[0] == '*':
var_line = cleandata.pop(i)
var_name = var_line.split('=', maxsplit=1)[0].strip()
var_value = var_line.split('=', maxsplit=1)[1].strip()

View File

@@ -4,6 +4,7 @@ Options supported:
- `+noall +answer` options are supported in cases where only the answer
information is desired.
- `+axfr` option is supported on its own
- `+nsid` option is supported
The `when_epoch` calculated timestamp field is naive. (i.e. based on the
local time of the system the parser is run on)
@@ -322,7 +323,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '2.4'
version = '2.5'
description = '`dig` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -427,6 +428,7 @@ def _parse_flags_line(flagsline):
def _parse_opt_pseudosection(optline):
# ;; OPT PSEUDOSECTION:
# ; EDNS: version: 0, flags:; udp: 4096
# ; NSID: 67 70 64 6e 73 2d 73 66 6f ("gpdns-sfo")
# ; COOKIE: 1cbc06703eaef210
if optline.startswith('; EDNS:'):
optline_list = optline.replace(',', ' ').split(';')
@@ -443,11 +445,18 @@ def _parse_opt_pseudosection(optline):
}
}
elif optline.startswith('; COOKIE:'):
if optline.startswith('; COOKIE:'):
return {
'cookie': optline.split()[2]
}
if optline.startswith('; NSID:'):
return {
'nsid': optline.split('("')[-1].rstrip('")')
}
return {}
def _parse_question(question):
# ;www.cnn.com. IN A

137
jc/parsers/find.py Normal file
View File

@@ -0,0 +1,137 @@
"""jc - JSON Convert `find` command output parser
This parser returns a list of objects by default and a list of strings if
the `--raw` option is used.
Usage (cli):
$ find | jc --find
Usage (module):
import jc
result = jc.parse('find', find_command_output)
Schema:
[
{
"path": string,
"node": string,
"error": string
}
]
Examples:
$ find | jc --find -p
[
{
"path": "./directory"
"node": "filename"
},
{
"path": "./anotherdirectory"
"node": "anotherfile"
},
{
"path": null
"node": null
"error": "find: './inaccessible': Permission denied"
}
...
]
$ find | jc --find -p -r
[
"./templates/readme_template",
"./templates/manpage_template",
"./.github/workflows/pythonapp.yml",
...
]
"""
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`find` command parser'
author = 'Solomon Leang'
author_email = 'solomonleang@gmail.com'
compatible = ['linux']
tags = ['command']
__version__ = info.version
def _process(proc_data):
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Strings) raw structured data to process
Returns:
List of Dictionaries. Structured data to conform to the schema.
"""
processed = []
for index in proc_data:
path, node, error = "", "", ""
if index == ".":
node = "."
elif index.startswith('find: '):
error = index
else:
try:
path, node = index.rsplit('/', maxsplit=1)
except ValueError:
pass
proc_line = {
'path': path if path else None,
'node': node if node else None
}
if error:
proc_line.update(
{'error': error}
)
processed.append(proc_line)
return processed
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:
List of raw strings or
List of Dictionaries of processed structured data
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output = []
if jc.utils.has_data(data):
raw_output = data.splitlines()
if raw:
return raw_output
else:
return _process(raw_output)

View File

@@ -149,11 +149,11 @@ from typing import List, Dict
import jc.utils
hash_pattern = re.compile(r'(?:[0-9]|[a-f]){40}')
changes_pattern = re.compile(r'\s(?P<files>\d+)\s+(files? changed),\s+(?P<insertions>\d+)\s(insertions?\(\+\))?(,\s+)?(?P<deletions>\d+)?(\s+deletions?\(\-\))?')
changes_pattern = re.compile(r'\s(?P<files>\d+)\s+(files? changed)(?:,\s+(?P<insertions>\d+)\s+(insertions?\(\+\)))?(?:,\s+(?P<deletions>\d+)\s+(deletions?\(\-\)))?')
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.3'
version = '1.4'
description = '`git log` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'

View File

@@ -83,12 +83,12 @@ from jc.exceptions import ParseError
hash_pattern = re.compile(r'(?:[0-9]|[a-f]){40}')
changes_pattern = re.compile(r'\s(?P<files>\d+)\s+(files? changed),\s+(?P<insertions>\d+)\s(insertions?\(\+\))?(,\s+)?(?P<deletions>\d+)?(\s+deletions?\(\-\))?')
changes_pattern = re.compile(r'\s(?P<files>\d+)\s+(files? changed)(?:,\s+(?P<insertions>\d+)\s+(insertions?\(\+\)))?(?:,\s+(?P<deletions>\d+)\s+(deletions?\(\-\)))?')
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.3'
version = '1.4'
description = '`git log` command streaming parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'

145
jc/parsers/ip_route.py Normal file
View File

@@ -0,0 +1,145 @@
"""jc - JSON Convert `ip route` command output parser
Usage (cli):
$ ip route | jc --ip-route
or
$ jc ip-route
Usage (module):
import jc
result = jc.parse('ip_route', ip_route_command_output)
Schema:
[
{
"ip": string,
"via": string,
"dev": string,
"metric": integer,
"proto": string,
"scope": string,
"src": string,
"via": string,
"status": string
}
]
Examples:
$ ip route | jc --ip-route -p
[
{
"ip": "10.0.2.0/24",
"dev": "enp0s3",
"proto": "kernel",
"scope": "link",
"src": "10.0.2.15",
"metric": 100
}
]
"""
from typing import Dict
import jc.utils
class info:
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`ip route` command parser'
author = 'Julian Jackson'
author_email = 'jackson.julian55@yahoo.com'
compatible = ['linux']
magic_commands = ['ip route']
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:
List of Json objects if data is processed and Raw data if raw = true.
"""
structure = {}
items = []
lines = data.splitlines()
index = 0
place = 0
inc = 0
for line in lines:
temp = line.split()
for word in temp:
if word == 'via':
y = {'via': temp[place + 1]}
place += 1
structure.update(y)
elif word == 'dev':
y = {'dev': temp[place + 1]}
place += 1
structure.update(y)
elif word == 'metric':
if raw:
y = {'metric': temp[place + 1]}
else:
y = {'metric': jc.utils.convert_to_int(temp[place+1])}
place += 1
structure.update(y)
elif word == 'proto':
y = {'proto': temp[place + 1]}
place += 1
structure.update(y)
elif word == 'scope':
y = {'scope': temp[place + 1]}
place += 1
structure.update(y)
elif word == 'src':
y = {'src': temp[place + 1]}
place += 1
structure.update(y)
elif word == 'status':
y = {'status': temp[place + 1]}
place += 1
structure.update(y)
elif word == 'default':
y = {'ip': 'default'}
place += 1
structure.update(y)
elif word == 'linkdown':
y = {'status': 'linkdown'}
place += 1
structure.update(y)
else:
y = {'ip': temp[0]}
place += 1
structure.update(y)
if y.get("ip") != "":
items.append(structure)
structure = {}
place = 0
index += 1
inc += 1
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
if not jc.utils.has_data(data):
return []
return items

View File

@@ -85,7 +85,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
version = '1.1'
description = '`iwconfig` command parser'
author = 'Thomas Vincent'
author_email = 'vrince@gmail.com'
@@ -146,7 +146,7 @@ def parse(
raw_output: List[Dict] = []
re_interface = re.compile(r'^(?P<name>[a-zA-Z0-9:._-]+)\s+(?P<protocol>([a-zA-Z0-9]+\s)*[a-zA-Z0-9.]+)\s+ESSID:\"(?P<essid>[a-zA-Z0-9:._\s]+)\"')
re_interface = re.compile(r'^(?P<name>[a-zA-Z0-9:._\-]+)\s+(?P<protocol>([a-zA-Z0-9]+\s)*[a-zA-Z0-9.]+)\s+ESSID:\"(?P<essid>[a-zA-Z0-9:._\s\-]+)\"')
re_mode = re.compile(r'Mode:(?P<mode>\w+)')
re_frequency = re.compile(r'Frequency:(?P<frequency>[0-9.]+)\s(?P<frequency_unit>\w+)')
re_access_point = re.compile(r'Access Point:\s*(?P<access_point>[0-9A-F:]+)')

View File

@@ -1,6 +1,6 @@
"""jc - JSON Convert `last` and `lastb` command output parser
Supports `-w` and `-F` options.
Supports `-w`, `-F`, and `-x` options.
Calculated epoch time fields are naive (i.e. based on the local time of the
system the parser is run on) since there is no timezone information in the
@@ -103,10 +103,15 @@ Examples:
import re
import jc.utils
DATE_RE = re.compile(r'[MTWFS][ouerha][nedritnu] [JFMASOND][aepuco][nbrynlgptvc]')
LAST_F_DATE_RE = re.compile(r'\d\d:\d\d:\d\d \d\d\d\d')
LOGIN_LOGOUT_EPOCH_RE = re.compile(r'.*\d\d:\d\d:\d\d \d\d\d\d.*')
LOGOUT_IGNORED_EVENTS = ['down', 'crash']
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.8'
version = '1.9'
description = '`last` and `lastb` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -138,9 +143,6 @@ def _process(proc_data):
if 'tty' in entry and entry['tty'] == '~':
entry['tty'] = None
if 'tty' in entry and entry['tty'] == 'system_boot':
entry['tty'] = 'system boot'
if 'hostname' in entry and entry['hostname'] == '-':
entry['hostname'] = None
@@ -153,11 +155,11 @@ def _process(proc_data):
if 'logout' in entry and entry['logout'] == 'gone_-_no_logout':
entry['logout'] = 'gone - no logout'
if 'login' in entry and re.match(r'.*\d\d:\d\d:\d\d \d\d\d\d.*', entry['login']):
if 'login' in entry and LOGIN_LOGOUT_EPOCH_RE.match(entry['login']):
timestamp = jc.utils.timestamp(entry['login'])
entry['login_epoch'] = timestamp.naive
if 'logout' in entry and re.match(r'.*\d\d:\d\d:\d\d \d\d\d\d.*', entry['logout']):
if 'logout' in entry and LOGIN_LOGOUT_EPOCH_RE.match(entry['logout']):
timestamp = jc.utils.timestamp(entry['logout'])
entry['logout_epoch'] = timestamp.naive
@@ -194,66 +196,71 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if not jc.utils.has_data(data):
return []
for entry in cleandata:
output_line = {}
for entry in cleandata:
output_line = {}
if (entry.startswith('wtmp begins ') or
entry.startswith('btmp begins ') or
entry.startswith('utx.log begins ')):
if any(
entry.startswith(f'{prefix} begins ')
for prefix in ['wtmp', 'btmp', 'utx.log']
):
continue
continue
entry = entry.replace('boot time', 'boot_time')
entry = entry.replace(' still logged in', '- still_logged_in')
entry = entry.replace(' gone - no logout', '- gone_-_no_logout')
entry = entry.replace('system boot', 'system_boot')
entry = entry.replace('boot time', 'boot_time')
entry = entry.replace(' still logged in', '- still_logged_in')
entry = entry.replace(' gone - no logout', '- gone_-_no_logout')
linedata = entry.split()
linedata = entry.split()
if re.match(r'[MTWFS][ouerha][nedritnu] [JFMASOND][aepuco][nbrynlgptvc]', ' '.join(linedata[2:4])):
linedata.insert(2, '-')
# Adding "-" before the date part.
if DATE_RE.match(' '.join(linedata[2:4])):
linedata.insert(2, '-')
# freebsd fix
if linedata[0] == 'boot_time':
linedata.insert(1, '-')
linedata.insert(1, '~')
# freebsd fix
if linedata[0] == 'boot_time':
linedata.insert(1, '-')
linedata.insert(1, '~')
output_line['user'] = linedata[0]
output_line['tty'] = linedata[1]
output_line['hostname'] = linedata[2]
output_line['user'] = linedata[0]
# last -F support
if re.match(r'\d\d:\d\d:\d\d \d\d\d\d', ' '.join(linedata[6:8])):
output_line['login'] = ' '.join(linedata[3:8])
# Fix for last -x (runlevel).
if output_line['user'] == 'runlevel' and linedata[1] == '(to':
linedata[1] += f' {linedata.pop(2)} {linedata.pop(2)}'
elif output_line['user'] in ['reboot', 'shutdown'] and linedata[1] == 'system': # system down\system boot
linedata[1] += f' {linedata.pop(2)}'
if len(linedata) > 9 and linedata[9] != 'crash' and linedata[9] != 'down':
output_line['tty'] = linedata[1]
output_line['hostname'] = linedata[2]
# last -F support
if LAST_F_DATE_RE.match(' '.join(linedata[6:8])):
output_line['login'] = ' '.join(linedata[3:8])
if len(linedata) > 9:
if linedata[9] not in LOGOUT_IGNORED_EVENTS:
output_line['logout'] = ' '.join(linedata[9:14])
if len(linedata) > 9 and (linedata[9] == 'crash' or linedata[9] == 'down'):
else:
output_line['logout'] = linedata[9]
# add more items to the list to line up duration
linedata.insert(10, '-')
linedata.insert(10, '-')
linedata.insert(10, '-')
linedata.insert(10, '-')
for _ in range(4):
linedata.insert(10, '-')
if len(linedata) > 14:
output_line['duration'] = linedata[14].replace('(', '').replace(')', '')
if len(linedata) > 14:
output_line['duration'] = linedata[14].replace('(', '').replace(')', '')
else: # normal last support
output_line['login'] = ' '.join(linedata[3:7])
# normal last support
else:
output_line['login'] = ' '.join(linedata[3:7])
if len(linedata) > 8:
output_line['logout'] = linedata[8]
if len(linedata) > 8:
output_line['logout'] = linedata[8]
if len(linedata) > 9:
output_line['duration'] = linedata[9].replace('(', '').replace(')', '')
if len(linedata) > 9:
output_line['duration'] = linedata[9].replace('(', '').replace(')', '')
raw_output.append(output_line)
raw_output.append(output_line)
if raw:
return raw_output
else:
return _process(raw_output)
return _process(raw_output)

162
jc/parsers/lsattr.py Normal file
View File

@@ -0,0 +1,162 @@
"""jc - JSON Convert `lsattr` command output parser
Usage (cli):
$ lsattr | jc --lsattr
or
$ jc lsattr
Usage (module):
import jc
result = jc.parse('lsattr', lsattr_command_output)
Schema:
Information from https://github.com/mirror/busybox/blob/2d4a3d9e6c1493a9520b907e07a41aca90cdfd94/e2fsprogs/e2fs_lib.c#L40
used to define field names
[
{
"file": string,
"compressed_file": Optional[boolean],
"compressed_dirty_file": Optional[boolean],
"compression_raw_access": Optional[boolean],
"secure_deletion": Optional[boolean],
"undelete": Optional[boolean],
"synchronous_updates": Optional[boolean],
"synchronous_directory_updates": Optional[boolean],
"immutable": Optional[boolean],
"append_only": Optional[boolean],
"no_dump": Optional[boolean],
"no_atime": Optional[boolean],
"compression_requested": Optional[boolean],
"encrypted": Optional[boolean],
"journaled_data": Optional[boolean],
"indexed_directory": Optional[boolean],
"no_tailmerging": Optional[boolean],
"top_of_directory_hierarchies": Optional[boolean],
"extents": Optional[boolean],
"no_cow": Optional[boolean],
"casefold": Optional[boolean],
"inline_data": Optional[boolean],
"project_hierarchy": Optional[boolean],
"verity": Optional[boolean],
}
]
Examples:
$ sudo lsattr /etc/passwd | jc --lsattr
[
{
"file": "/etc/passwd",
"extents": true
}
]
"""
from typing import List, Dict
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`lsattr` command parser'
author = 'Mark Rotner'
author_email = 'rotner.mr@gmail.com'
compatible = ['linux']
magic_commands = ['lsattr']
tags = ['command']
__version__ = info.version
ERROR_PREFIX = "lsattr:"
# https://github.com/mirror/busybox/blob/2d4a3d9e6c1493a9520b907e07a41aca90cdfd94/e2fsprogs/e2fs_lib.c#L40
# https://github.com/landley/toybox/blob/f1682dc79fd75f64042b5438918fe5a507977e1c/toys/other/lsattr.c#L97
ATTRIBUTES = {
"B": "compressed_file",
"Z": "compressed_dirty_file",
"X": "compression_raw_access",
"s": "secure_deletion",
"u": "undelete",
"S": "synchronous_updates",
"D": "synchronous_directory_updates",
"i": "immutable",
"a": "append_only",
"d": "no_dump",
"A": "no_atime",
"c": "compression_requested",
"E": "encrypted",
"j": "journaled_data",
"I": "indexed_directory",
"t": "no_tailmerging",
"T": "top_of_directory_hierarchies",
"e": "extents",
"C": "no_cow",
"F": "casefold",
"N": "inline_data",
"P": "project_hierarchy",
"V": "verity",
}
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> List[JSONDictType]:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
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)
output: List = []
cleandata = list(filter(None, data.splitlines()))
if not jc.utils.has_data(data):
return output
for line in cleandata:
# -R flag returns the output in the format:
# Folder:
# attributes file_in_folder
if line.endswith(':'):
continue
# lsattr: Operation not supported ....
if line.startswith(ERROR_PREFIX):
continue
line_output: Dict = {}
# attributes file
# --------------e----- /etc/passwd
attributes, file = line.split()
line_output['file'] = file
for attribute in list(attributes):
attribute_key = ATTRIBUTES.get(attribute)
if attribute_key:
line_output[attribute_key] = True
if line_output:
output.append(line_output)
return output

View File

@@ -97,6 +97,24 @@ Schema:
]
}
},
"cdc_mbim": {
"<item>": {
"value": string,
"description": string,
"attributes": [
string
]
}
},
"cdc_mbim_extended": {
"<item>": {
"value": string,
"description": string,
"attributes": [
string
]
}
},
"videocontrol_descriptors": [
{
"<item>": {
@@ -291,7 +309,7 @@ from jc.exceptions import ParseError
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.3'
version = '1.4'
description = '`lsusb` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -500,6 +518,8 @@ class _LsUsb():
self.cdc_call_management = _descriptor_obj('cdc_call_management')
self.cdc_acm = _descriptor_obj('cdc_acm')
self.cdc_union = _descriptor_obj('cdc_union')
self.cdc_mbim = _descriptor_obj('cdc_mbim')
self.cdc_mbim_extended = _descriptor_obj('cdc_mbim_extended')
self.endpoint_descriptors = _descriptor_list('endpoint_descriptor')
self.videocontrol_interface_descriptors = _descriptor_list('videocontrol_interface_descriptor')
self.videostreaming_interface_descriptors = _descriptor_list('videostreaming_interface_descriptor')
@@ -538,7 +558,8 @@ class _LsUsb():
section_header = self.normal_section_header
if self.section == 'videocontrol_interface_descriptor' \
or self.section == 'videostreaming_interface_descriptor':
or self.section == 'videostreaming_interface_descriptor' \
or self.section == 'cdc_mbim_extended':
section_header = self.larger_section_header
@@ -689,6 +710,8 @@ class _LsUsb():
' CDC Union:': 'cdc_union',
' HID Device Descriptor:': 'hid_device_descriptor',
' Report Descriptors:': 'report_descriptors',
' CDC MBIM:': 'cdc_mbim',
' CDC MBIM Extended:': 'cdc_mbim_extended',
'Hub Descriptor:': 'hub_descriptor',
' Hub Port Status:': 'hub_port_status',
'Device Qualifier (for other device speed):': 'device_qualifier',
@@ -713,6 +736,8 @@ class _LsUsb():
'cdc_call_management': self.cdc_call_management.list,
'cdc_acm': self.cdc_acm.list,
'cdc_union': self.cdc_union.list,
'cdc_mbim': self.cdc_mbim.list,
'cdc_mbim_extended': self.cdc_mbim_extended.list,
'hid_device_descriptor': self.hid_device_descriptor.list,
# 'report_descriptors': self.report_descriptors_list, # not implemented
'videocontrol_interface_descriptor': self.videocontrol_interface_descriptors.list,
@@ -757,6 +782,8 @@ class _LsUsb():
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['cdc_call_management'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['cdc_acm'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['cdc_union'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['cdc_mbim'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['cdc_mbim_extended'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['hid_device_descriptor'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['endpoint_descriptors'] = []
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['endpoint_descriptors'][0] = {}
@@ -847,6 +874,12 @@ class _LsUsb():
if self.cdc_union._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.cdc_union._update_output(idx, iface_idx, i_desc_obj)
if self.cdc_mbim._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.cdc_mbim._update_output(idx, iface_idx, i_desc_obj)
if self.cdc_mbim_extended._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.cdc_mbim_extended._update_output(idx, iface_idx, i_desc_obj)
if self.hid_device_descriptor._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.hid_device_descriptor._update_output(idx, iface_idx, i_desc_obj)
@@ -923,6 +956,10 @@ def parse(data, raw=False, quiet=False):
lsusb = _LsUsb()
if jc.utils.has_data(data):
# fix known too-long field names
data = data.replace('bmNetworkCapabilities', 'bmNetworkCapabilit ')
for line in data.splitlines():
# only -v option or no options are supported
if line.startswith('/'):

View File

@@ -355,17 +355,18 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.13'
version = '1.14'
description = '`netstat` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'freebsd']
compatible = ['linux', 'darwin', 'freebsd', 'win32']
magic_commands = ['netstat']
tags = ['command']
__version__ = info.version
WINDOWS_NETSTAT_HEADER = "Active Connections"
def _process(proc_data):
"""
@@ -450,9 +451,10 @@ def parse(data, raw=False, quiet=False):
import jc.parsers.netstat_freebsd_osx
raw_output = jc.parsers.netstat_freebsd_osx.parse(cleandata)
# use linux parser
else:
elif cleandata[0] == WINDOWS_NETSTAT_HEADER: # use windows parser.
import jc.parsers.netstat_windows
raw_output = jc.parsers.netstat_windows.parse(cleandata)
else: # use linux parser.
import jc.parsers.netstat_linux
raw_output = jc.parsers.netstat_linux.parse(cleandata)

View File

@@ -0,0 +1,75 @@
"""
jc - JSON Convert Windows `netstat` command output parser
"""
from typing import Dict, List
POSSIBLE_PROTOCOLS = ("TCP", "UDP", "TCPv6", "UDPv6")
def normalize_headers(headers: str):
"""
Normalizes the headers to match the jc netstat parser style
(local_address -> local_address, local_port...).
"""
headers = headers.lower().strip()
headers = headers.replace("local address", "local_address")
headers = headers.replace("foreign address", "foreign_address")
return headers.split()
def parse(cleandata: List[str]):
"""
Main text parsing function for Windows netstat
Parameters:
cleandata: (string) text data to parse
Returns:
List of Dictionaries. Raw structured data.
"""
raw_output = []
cleandata.pop(0) # Removing the "Active Connections" header.
headers = normalize_headers(cleandata.pop(0))
for line in cleandata:
line = line.strip()
if not line.startswith(POSSIBLE_PROTOCOLS): # -b.
line_data = raw_output.pop(len(raw_output) - 1)
line_data['program_name'] = line
raw_output.append(line_data)
continue
line_data = line.split()
line_data: Dict[str, str] = dict(zip(headers, line_data))
for key in list(line_data.keys()):
if key == "local_address":
local_address, local_port = line_data[key].rsplit(
":", maxsplit=1)
line_data["local_address"] = local_address
line_data["local_port"] = local_port
continue
if key == "foreign_address":
foreign_address, foreign_port = line_data[key].rsplit(
":", maxsplit=1)
line_data["foreign_address"] = foreign_address
line_data["foreign_port"] = foreign_port
continue
# There is no state in UDP, so the data after the "state" header will leak.
if key == "proto" and "state" in headers and line_data["proto"] == "UDP":
next_header = headers.index("state") + 1
if len(headers) > next_header:
next_header = headers[next_header]
line_data[next_header] = line_data["state"]
line_data["state"] = ''
raw_output.append(line_data)
return raw_output

View File

@@ -96,14 +96,14 @@ class PBParser(object):
prefix = self.data[0:6]
for case in Switch(prefix):
if case('bplist'):
self.file_type = 'binary'
import biplist
parsed_plist = biplist.readPlist(self.file_path)
# self.file_type = 'binary'
# import biplist
# parsed_plist = biplist.readPlist(self.file_path)
break
if case('<?xml '):
self.file_type = 'xml'
import plistlib
parsed_plist = plistlib.readPlist(self.file_path)
# self.file_type = 'xml'
# import plistlib
# parsed_plist = plistlib.readPlist(self.file_path)
break
if case():
self.file_type = 'ascii'
@@ -111,7 +111,7 @@ class PBParser(object):
if self.data[0:2] == '//':
# this is to try to see if we can locate the desired string encoding of the file
import re
result = re.search('^// !\$\*(.+?)\*\$!', self.data) # pylint: disable=anomalous-backslash-in-string
result = re.search(r'^// !\$\*(.+?)\*\$!', self.data) # pylint: disable=anomalous-backslash-in-string
if result:
self.string_encoding = result.group(1)
#now return the parse

View File

@@ -57,7 +57,7 @@ def KeySorter(obj1, obj2):
result = StringCmp(str(obj1), str(obj2))
return result
class pbRoot(collections.MutableMapping):
class pbRoot(collections.abc.MutableMapping):
def __init__(self, *args, **kwargs):
self.store = dict()

View File

@@ -164,7 +164,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.8'
version = '1.9'
description = '`ping` and `ping6` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -284,6 +284,8 @@ def _linux_parse(data):
if ipv4 and linedata[0][5] not in string.digits:
hostname = True
# fixup for missing hostname
linedata[0] = linedata[0][:5] + 'nohost' + linedata[0][5:]
elif ipv4 and linedata[0][5] in string.digits:
hostname = False
elif not ipv4 and ' (' in linedata[0]:
@@ -314,7 +316,8 @@ def _linux_parse(data):
if line.startswith('---'):
footer = True
raw_output['destination'] = line.split()[1]
if line[4] != ' ': # fixup for missing hostname
raw_output['destination'] = line.split()[1]
continue
if footer:

View File

@@ -85,7 +85,7 @@ from jc.exceptions import ParseError
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.2'
version = '1.3'
description = '`ping` and `ping6` command streaming parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -340,6 +340,8 @@ def _linux_parse(line, s):
if s.ipv4 and line[5] not in string.digits:
s.hostname = True
# fixup for missing hostname
line = line[:5] + 'nohost' + line[5:]
elif s.ipv4 and line[5] in string.digits:
s.hostname = False
elif not s.ipv4 and ' (' in line:

View File

@@ -194,6 +194,7 @@ def parse(
net_packet_p = re.compile(r'^sk RefCnt Type Proto Iface R Rmem User Inode\n')
net_protocols_p = re.compile(r'^protocol size sockets memory press maxhdr slab module cl co di ac io in de sh ss gs se re sp bi br ha uh gp em\n')
net_route_p = re.compile(r'^Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT\s+\n')
net_tcp_p = re.compile(r'^\s+sl\s+local_address\s+(?:rem_address|remote_address)\s+st\s+tx_queue\s+rx_queue\s+tr\s+tm->when\s+retrnsmt\s+uid\s+timeout\s+inode')
net_unix_p = re.compile(r'^Num RefCount Protocol Flags Type St Inode Path\n')
pid_fdinfo_p = re.compile(r'^pos:\t\d+\nflags:\t\d+\nmnt_id:\t\d+\n')
@@ -204,7 +205,7 @@ def parse(
pid_smaps_p = re.compile(r'^[0-9a-f]{12}-[0-9a-f]{12} [rwxsp\-]{4} [0-9a-f]{8} [0-9a-f]{2}:[0-9a-f]{2} \d+ [^\n]+\nSize:\s+\d+ \S\S\n')
pid_stat_p = re.compile(r'^\d+ \(.+\) \S \d+ \d+ \d+ \d+ -?\d+ (?:\d+ ){43}\d+$', re.DOTALL)
pid_statm_p = re.compile(r'^\d+ \d+ \d+\s\d+\s\d+\s\d+\s\d+$')
pid_status_p = re.compile(r'^Name:\t.+\nUmask:\t\d+\nState:\t.+\nTgid:\t\d+\n')
pid_status_p = re.compile(r'^Name:\t.+\n(?:Umask:\t\d+\n)?State:\t.+\nTgid:\t\d+\n')
# scsi_device_info = re.compile(r"^'\w+' '.+' 0x\d+")
# scsi_scsi_p = re.compile(r'^Attached devices:\nHost: \w+ ')
@@ -249,6 +250,7 @@ def parse(
net_packet_p: 'proc_net_packet',
net_protocols_p: 'proc_net_protocols',
net_route_p: 'proc_net_route',
net_tcp_p: 'proc_net_tcp',
net_unix_p: 'proc_net_unix',
net_ipv6_route_p: 'proc_net_ipv6_route', # before net_dev_mcast
net_dev_mcast_p: 'proc_net_dev_mcast', # after net_ipv6_route

293
jc/parsers/proc_net_tcp.py Normal file
View File

@@ -0,0 +1,293 @@
"""jc - JSON Convert `/proc/net/tcp` and `proc/net/tcp6` file parser
IPv4 and IPv6 addresses are converted to standard notation unless the raw
(--raw) option is used.
Usage (cli):
$ cat /proc/net/tcp | jc --proc
or
$ jc /proc/net/tcp
or
$ cat /proc/net/tcp | jc --proc-net-tcp
Usage (module):
import jc
result = jc.parse('proc', proc_net_tcp_file)
or
import jc
result = jc.parse('proc_net_tcp', proc_net_tcp_file)
Schema:
Field names and types gathered from the following:
https://www.kernel.org/doc/Documentation/networking/proc_net_tcp.txt
https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_ipv4.c
https://github.com/torvalds/linux/blob/master/net/ipv6/tcp_ipv6.c
[
{
"entry": integer,
"local_address": string,
"local_port": integer,
"remote_address": string,
"remote_port": integer,
"state": string,
"tx_queue": string,
"rx_queue": string,
"timer_active": integer,
"jiffies_until_timer_expires": string,
"unrecovered_rto_timeouts": string,
"uid": integer,
"unanswered_0_window_probes": integer,
"inode": integer,
"sock_ref_count": integer,
"sock_mem_loc": string,
"retransmit_timeout": integer,
"soft_clock_tick": integer,
"ack_quick_pingpong": integer,
"sending_congestion_window": integer,
"slow_start_size_threshold": integer
}
]
Examples:
$ cat /proc/net/tcp | jc --proc -p
[
{
"entry": "0",
"local_address": "10.0.0.28",
"local_port": 42082,
"remote_address": "64.12.0.108",
"remote_port": 80,
"state": "04",
"tx_queue": "00000001",
"rx_queue": "00000000",
"timer_active": 1,
"jiffies_until_timer_expires": "00000015",
"unrecovered_rto_timeouts": "00000000",
"uid": 0,
"unanswered_0_window_probes": 0,
"inode": 0,
"sock_ref_count": 3,
"sock_mem_loc": "ffff8c7a0de930c0",
"retransmit_timeout": 21,
"soft_clock_tick": 4,
"ack_quick_pingpong": 30,
"sending_congestion_window": 10,
"slow_start_size_threshold": -1
},
{
"entry": "1",
"local_address": "10.0.0.28",
"local_port": 38864,
"remote_address": "104.244.42.65",
"remote_port": 80,
"state": "06",
"tx_queue": "00000000",
"rx_queue": "00000000",
"timer_active": 3,
"jiffies_until_timer_expires": "000007C5",
"unrecovered_rto_timeouts": "00000000",
"uid": 0,
"unanswered_0_window_probes": 0,
"inode": 0,
"sock_ref_count": 3,
"sock_mem_loc": "ffff8c7a12d31aa0"
},
...
]
$ cat /proc/net/tcp | jc --proc -p -r
[
{
"entry": "1",
"local_address": "1C00000A",
"local_port": "A462",
"remote_address": "6C000C40",
"remote_port": "0050",
"state": "04",
"tx_queue": "00000001",
"rx_queue": "00000000",
"timer_active": "01",
"jiffies_until_timer_expires": "00000015",
"unrecovered_rto_timeouts": "00000000",
"uid": "0",
"unanswered_0_window_probes": "0",
"inode": "0",
"sock_ref_count": "3",
"sock_mem_loc": "ffff8c7a0de930c0",
"retransmit_timeout": "21",
"soft_clock_tick": "4",
"ack_quick_pingpong": "30",
"sending_congestion_window": "10",
"slow_start_size_threshold": "-1"
},
{
"entry": "2",
"local_address": "1C00000A",
"local_port": "97D0",
"remote_address": "412AF468",
"remote_port": "0050",
"state": "06",
"tx_queue": "00000000",
"rx_queue": "00000000",
"timer_active": "03",
"jiffies_until_timer_expires": "000007C5",
"unrecovered_rto_timeouts": "00000000",
"uid": "0",
"unanswered_0_window_probes": "0",
"inode": "0",
"sock_ref_count": "3",
"sock_mem_loc": "ffff8c7a12d31aa0"
},
...
]
"""
import binascii
import socket
import struct
from typing import List, Dict
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`/proc/net/tcp` and `/proc/net/tcp6` file parser'
author = 'Alvin Solomon'
author_email = 'alvinms01@gmail.com'
compatible = ['linux']
tags = ['file']
hidden = True
__version__ = info.version
def hex_to_ip(hexaddr: str) -> str:
if len(hexaddr) == 8:
addr_long = int(hexaddr, 16)
return socket.inet_ntop(socket.AF_INET, struct.pack("<L", addr_long))
elif len(hexaddr) == 32:
addr = binascii.a2b_hex(hexaddr)
addr_tup = struct.unpack('>IIII', addr)
addr_bytes = struct.pack('@IIII', *addr_tup)
return socket.inet_ntop(socket.AF_INET6, addr_bytes)
return ''
def _process(proc_data: List[Dict]) -> List[Dict]:
"""
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.
"""
int_list = {
'timer_active', 'uid', 'unanswered_0_window_probes', 'inode',
'sock_ref_count', 'retransmit_timeout', 'soft_clock_tick',
'ack_quick_pingpong', 'sending_congestion_window',
'slow_start_size_threshold'
}
for entry in proc_data:
if 'local_address' in entry:
entry['local_address'] = hex_to_ip(entry['local_address'])
entry['local_port'] = int(entry['local_port'], 16)
entry['remote_address'] = hex_to_ip(entry['remote_address'])
entry['remote_port'] = int(entry['remote_port'], 16)
for item in int_list:
if item in entry:
entry[item] = jc.utils.convert_to_int(entry[item])
return proc_data
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> List[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:
List of Dictionaries. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List = []
if jc.utils.has_data(data):
line_data = data.splitlines()[1:]
for entry in line_data:
line = entry.split()
output_line = {}
output_line['entry'] = line[0][:-1]
local_ip_port = line[1]
local_ip = local_ip_port.split(':')[0]
local_port = local_ip_port.split(':')[1]
output_line['local_address'] = local_ip
output_line['local_port'] = local_port
remote_ip_port = line[2]
remote_ip = remote_ip_port.split(':')[0]
remote_port = remote_ip_port.split(':')[1]
output_line['remote_address'] = remote_ip
output_line['remote_port'] = remote_port
output_line['state'] = line[3]
output_line['tx_queue'] = line[4][:8]
output_line['rx_queue'] = line[4][9:]
output_line['timer_active'] = line[5][:2]
output_line['jiffies_until_timer_expires'] = line[5][3:]
output_line['unrecovered_rto_timeouts'] = line[6]
output_line['uid'] = line[7]
output_line['unanswered_0_window_probes'] = line[8]
output_line['inode'] = line[9]
output_line['sock_ref_count'] = line[10]
output_line['sock_mem_loc'] = line[11]
# fields not always included
if len(line) > 12:
output_line['retransmit_timeout'] = line[12]
output_line['soft_clock_tick'] = line[13]
output_line['ack_quick_pingpong'] = line[14]
output_line['sending_congestion_window'] = line[15]
output_line['slow_start_size_threshold'] = line[16]
raw_output.append(output_line)
return raw_output if raw else _process(raw_output)

171
jc/parsers/resolve_conf.py Normal file
View File

@@ -0,0 +1,171 @@
"""jc - JSON Convert `/etc/resolve.conf` file parser
This parser may be more forgiving than the system parser. For example, if
multiple `search` lists are defined, this parser will append all entries to
the `search` field, while the system parser may only use the list from the
last defined instance.
Usage (cli):
$ cat /etc/resolve.conf | jc --resolve-conf
Usage (module):
import jc
result = jc.parse('resolve_conf', resolve_conf_output)
Schema:
{
"domain": string,
"search": [
string
],
"nameservers": [
string
],
"options": [
string
],
"sortlist": [
string
]
}
Examples:
$ cat /etc/resolve.conf | jc --resolve-conf -p
{
"search": [
"eng.myprime.com",
"dev.eng.myprime.com",
"labs.myprime.com",
"qa.myprime.com"
],
"nameservers": [
"10.136.17.15"
],
"options": [
"rotate",
"ndots:1"
]
}
"""
import re
from typing import List, Dict
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`/etc/resolve.conf` file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['file']
__version__ = info.version
def _process(proc_data: JSONDictType) -> JSONDictType:
"""
Final processing to conform to the schema.
Parameters:
proc_data: Dictionary raw structured data to process
Returns:
Dictionary. Structured to conform to the schema.
"""
return proc_data
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> 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:
Dictionary. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: Dict = {}
search: List[str] = []
nameservers: List[str] = []
options: List[str] = []
sortlist: List[str] = []
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
# comments start with # or ; and can be inline
if '#' in line or ';' in line:
userdata = list(filter(None, re.split("[#;]+", line, maxsplit=1)))
userdata = [x for x in userdata if x.strip()]
if len(userdata) <= 1: # whole line is a comment
continue
userdata_str = userdata[0].strip()
else:
userdata_str = line.strip()
if userdata_str.startswith('domain'):
raw_output['domain'] = userdata_str.split()[1].strip()
continue
if userdata_str.startswith('search'):
search_items = userdata_str.split(maxsplit=1)[1]
search_list = search_items.split()
search.extend(search_list)
continue
if userdata_str.startswith('nameserver'):
ns_str = userdata_str.split()[1]
nameservers.append(ns_str)
continue
if userdata_str.startswith('options'):
option_items = userdata_str.split(maxsplit=1)[1]
option_list = option_items.split()
options.extend(option_list)
continue
if userdata_str.startswith('sortlist'):
sortlist_items = userdata_str.split(maxsplit=1)[1]
sortlist_list = sortlist_items.split()
sortlist.extend(sortlist_list)
continue
if search:
raw_output['search'] = search
if nameservers:
raw_output['nameservers'] = nameservers
if options:
raw_output['options'] = options
if sortlist:
raw_output['sortlist'] = sortlist
return raw_output if raw else _process(raw_output)

View File

@@ -17,6 +17,13 @@ Schema:
[
{
"interfaces": [
{
"id": string,
"mac": string,
"name": string,
}
]
"destination": string,
"gateway": string,
"genmask": string,
@@ -109,11 +116,11 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.8'
version = '1.9'
description = '`route` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux']
compatible = ['linux', 'win32']
magic_commands = ['route']
tags = ['command']
@@ -152,6 +159,14 @@ def _process(proc_data):
if key in int_list:
entry[key] = jc.utils.convert_to_int(entry[key])
if 'interfaces' in entry:
interfaces = []
for interface in entry["interfaces"]:
# 00 ff 58 60 5f 61 -> 00:ff:58:60:5f:61
interface['mac'] = interface['mac'].replace(' ', ':').replace('.', '')
interfaces.append(interface)
entry["interfaces"] = interfaces
# add flags_pretty
# Flag mapping from https://www.man7.org/linux/man-pages/man8/route.8.html
if 'flags' in entry:
@@ -165,6 +180,16 @@ def _process(proc_data):
return proc_data
def normalize_headers(headers: str):
# fixup header row for ipv6
if ' Next Hop ' in headers:
headers = headers.replace(' If', ' Iface')
headers = headers.replace(' Next Hop ', ' Next_Hop ')
headers = headers.replace(' Flag ', ' Flags ')
headers = headers.replace(' Met ', ' Metric ')
headers = headers.lower()
return headers
def parse(data, raw=False, quiet=False):
"""
@@ -180,24 +205,22 @@ def parse(data, raw=False, quiet=False):
List of Dictionaries. Raw or processed structured data.
"""
import jc.utils
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
cleandata = data.splitlines()[1:]
cleandata = data.splitlines()
raw_output = []
if jc.utils.has_data(data):
# fixup header row for ipv6
if ' Next Hop ' in cleandata[0]:
cleandata[0] = cleandata[0].replace(' If', ' Iface')
cleandata[0] = cleandata[0].replace(' Next Hop ', ' Next_Hop ')\
.replace(' Flag ', ' Flags ')\
.replace(' Met ', ' Metric ')
cleandata[0] = cleandata[0].lower()
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
import jc.parsers.route_windows
if cleandata[0] in jc.parsers.route_windows.SEPERATORS:
raw_output = jc.parsers.route_windows.parse(cleandata)
else:
cleandata.pop(0) # Removing "Kernel IP routing table".
cleandata[0] = normalize_headers(cleandata[0])
import jc.parsers.universal
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if raw:
return raw_output

128
jc/parsers/route_windows.py Normal file
View File

@@ -0,0 +1,128 @@
"""
jc - JSON Convert Windows `route` command output parser
"""
import re
from typing import List
SEPERATORS = (
"===========================================================================",
" None"
)
# 22...00 50 56 c0 00 01 ......VMware Virtual Ethernet Adapter for VMnet1
# {"id": 22, "mac": "00 50 56 c0 00 01", "name": "VMware Virtual Ethernet Adapter for VMnet1"}
INTERFACE_REGEX = re.compile(
r"^(?P<id>\d+)\.{3}(?P<mac>.{17})[\s+\.]+(?P<name>[^\n\r]+)$"
)
ROUTE_TABLES = ("IPv4 Route Table", "IPv6 Route Table")
ROUTE_TYPES = ("Active Routes:", "Persistent Routes:")
def get_lines_until_seperator(iterator):
lines = []
for line in iterator:
if line in SEPERATORS:
break
lines.append(line)
return lines
def normalize_route_table(route_table: List[str]):
headers = route_table[0]
headers = headers.lower()
headers = headers.replace("network destination", "destination")
headers = headers.replace("if", "iface")
headers = headers.replace("interface", "iface")
headers = headers.replace("netmask", "genmask")
headers_count = len(headers.split())
previous_line_has_all_the_data = True
normalized_route_table = [headers]
for row in route_table[1:]:
row = row.strip()
has_all_the_data = len(row.split()) == headers_count
# If the number of columns doesn't match the number of headers in the current and previous line, concatenating them.
if not has_all_the_data and not previous_line_has_all_the_data:
previous_line = normalized_route_table.pop(
len(normalized_route_table) - 1)
row = f'{previous_line} {row}'
has_all_the_data = True
normalized_route_table.append(row.strip())
previous_line_has_all_the_data = has_all_the_data
return normalized_route_table
def parse(cleandata: List[str]):
"""
Main text parsing function for Windows route
Parameters:
cleandata: (string) text data to parse
Returns:
List of Dictionaries. Raw structured data.
"""
raw_output = []
data_iterator = iter(cleandata)
for line in data_iterator:
if not line:
continue
if line == "Interface List":
# Interface List
# 8...00 ff 58 60 5f 61 ......TAP-Windows Adapter V9
# 52...00 15 5d fd 0d 45 ......Hyper-V Virtual Ethernet Adapter
# ===========================================================================
interfaces = []
for interface_line in data_iterator:
interface_line = interface_line.strip()
if interface_line in SEPERATORS:
break
interface_match = INTERFACE_REGEX.search(interface_line)
if interface_match:
interfaces.append(interface_match.groupdict())
if interfaces:
raw_output.append({"interfaces": interfaces})
continue
full_route_table = []
if line in ROUTE_TABLES:
next(data_iterator) # Skipping the table title.
# Persistent Routes:
# Network Address Netmask Gateway Address Metric
# 157.0.0.0 255.0.0.0 157.55.80.1 3
# ===========================================================================
for route_line in data_iterator:
if route_line in ROUTE_TYPES:
import jc.parsers.universal
route_table = get_lines_until_seperator(
data_iterator
)
if not route_table:
continue
route_table = normalize_route_table(
route_table
)
full_route_table.extend(
jc.parsers.universal.simple_table_parse(
route_table
)
)
raw_output.extend(full_route_table)
return raw_output

281
jc/parsers/srt.py Normal file
View File

@@ -0,0 +1,281 @@
"""jc - JSON Convert `SRT` file parser
Usage (cli):
$ cat foo.srt | jc --srt
Usage (module):
import jc
result = jc.parse('srt', srt_file_output)
Schema:
[
{
"index": int,
"start": {
"hours": int,
"minutes": int,
"seconds": int,
"milliseconds": int,
"timestamp": string
},
"end": {
"hours": int,
"minutes": int,
"seconds": int,
"milliseconds": int,
"timestamp": string
},
"content": string
}
]
Examples:
$ cat attack_of_the_clones.srt
1
00:02:16,612 --> 00:02:19,376
Senator, we're making
our final approach into Coruscant.
2
00:02:19,482 --> 00:02:21,609
Very good, Lieutenant.
...
$ cat attack_of_the_clones.srt | jc --srt
[
{
"index": 1,
"start": {
"hours": 0,
"minutes": 2,
"seconds": 16,
"milliseconds": 612,
"timestamp": "00:02:16,612"
},
"end": {
"hours": 0,
"minutes": 2,
"seconds": 19,
"milliseconds": 376,
"timestamp": "00:02:19,376"
},
"content": "Senator, we're making\nour final approach into Coruscant."
},
{
"index": 2,
"start": {
"hours": 0,
"minutes": 2,
"seconds": 19,
"milliseconds": 482,
"timestamp": "00:02:19,482"
},
"end": {
"hours": 0,
"minutes": 2,
"seconds": 21,
"milliseconds": 609,
"timestamp": "00:02:21,609"
},
"content": "Very good, Lieutenant."
},
...
]
"""
import jc.utils
import re
from typing import List, Dict
from jc.jc_types import JSONDictType
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = 'SRT file parser'
author = 'Mark Rotner'
author_email = 'rotner.mr@gmail.com'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['standard', 'file', 'string']
__version__ = info.version
# Regex from https://github.com/cdown/srt/blob/434d0c1c9d5c26d5c3fb1ce979fc05b478e9253c/srt.py#LL16C1.
# The MIT License
# Copyright (c) 2014-present Christopher Down
# 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.
# The format: (int)index\n(timestamp)start --> (timestamp)end\n(str)content\n.
# Example:
# 1
# 00:02:16,612 --> 00:02:19,376
# Senator, we're making our final approach into Coruscant.
# Start & End timestamp format: hours:minutes:seconds,millisecond.
# "." is not technically valid as a delimiter, but many editors create SRT
# files with this delimiter for whatever reason. Many editors and players
# accept it, so we do too.
RGX_TIMESTAMP_MAGNITUDE_DELIM = r"[,.:,.。:]"
RGX_POSITIVE_INT = r"[0-9]+"
RGX_POSITIVE_INT_OPTIONAL = r"[0-9]*"
RGX_TIMESTAMP = '{field}{separator}{field}{separator}{field}{separator}?{optional_field}'.format(
separator=RGX_TIMESTAMP_MAGNITUDE_DELIM,
field=RGX_POSITIVE_INT,
optional_field=RGX_POSITIVE_INT_OPTIONAL
)
RGX_INDEX = r"-?[0-9]+\.?[0-9]*" # int\float\negative.
RGX_CONTENT = r".*?" # Anything(except newline) but lazy.
RGX_NEWLINE = r"\r?\n" # Newline(CRLF\LF).
SRT_REGEX = re.compile(
r"\s*(?:({index})\s*{newline})?({ts}) *-[ -] *> *({ts}) ?(?:{newline}|\Z)({content})"
# Many sub editors don't add a blank line to the end, and many editors and
# players accept that. We allow it to be missing in input.
#
# We also allow subs that are missing a double blank newline. This often
# happens on subs which were first created as a mixed language subtitle,
# for example chs/eng, and then were stripped using naive methods (such as
# ed/sed) that don't understand newline preservation rules in SRT files.
#
# This means that when you are, say, only keeping chs, and the line only
# contains english, you end up with not only no content, but also all of
# the content lines are stripped instead of retaining a newline.
r"(?:{newline}|\Z)(?:{newline}|\Z|(?=(?:{index}\s*{newline}{ts})))"
# Some SRT blocks, while this is technically invalid, have blank lines
# inside the subtitle content. We look ahead a little to check that the
# next lines look like an index and a timestamp as a best-effort
# solution to work around these.
r"(?=(?:(?:{index}\s*{newline})?{ts}|\Z))".format(
index=RGX_INDEX,
ts=RGX_TIMESTAMP,
content=RGX_CONTENT,
newline=RGX_NEWLINE,
),
re.DOTALL,
)
TIMESTAMP_REGEX = re.compile(
'^({field}){separator}({field}){separator}({field}){separator}?({optional_field})$'.format(
separator=RGX_TIMESTAMP_MAGNITUDE_DELIM,
field=RGX_POSITIVE_INT,
optional_field=RGX_POSITIVE_INT_OPTIONAL
)
)
def _process(proc_data: List[JSONDictType]) -> List[JSONDictType]:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (Dictionary) raw structured data to process
Returns:
List of Dictionaries representing an SRT document.
"""
int_list = {'index'}
timestamp_list = {"start", "end"}
timestamp_int_list = {"hours", "minutes", "seconds", "milliseconds"}
for entry in proc_data:
# Converting {"index"} to int.
for key in entry:
if key in int_list:
entry[key] = jc.utils.convert_to_int(entry[key])
# Converting {"hours", "minutes", "seconds", "milliseconds"} to int.
if key in timestamp_list:
timestamp = entry[key]
for timestamp_key in timestamp:
if timestamp_key in timestamp_int_list:
timestamp[timestamp_key] = jc.utils.convert_to_int(
timestamp[timestamp_key])
return proc_data
def parse_timestamp(timestamp: str) -> Dict:
"""
timestamp: "hours:minutes:seconds,milliseconds" --->
{
"hours": "hours",
"minutes": "minutes",
"seconds": "seconds",
"milliseconds": "milliseconds",
"timestamp": "hours:minutes:seconds,milliseconds"
}
"""
ts_match = TIMESTAMP_REGEX.match(timestamp)
if ts_match:
hours, minutes, seconds, milliseconds = ts_match.groups()
return {
"hours": hours,
"minutes": minutes,
"seconds": seconds,
"milliseconds": milliseconds,
"timestamp": timestamp
}
return {}
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:
Dictionary. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List[Dict] = []
if not jc.utils.has_data(data):
return raw_output
for subtitle in SRT_REGEX.finditer(data):
index, start, end, content = subtitle.groups()
raw_output.append(
{
"index": index,
"start": parse_timestamp(start),
"end": parse_timestamp(end),
"content": content.replace("\r\n", "\n")
}
)
return raw_output if raw else _process(raw_output)

View File

@@ -1,8 +1,5 @@
"""jc - JSON Convert `ss` command output parser
Extended information options like `-e` and `-p` are not supported and may
cause parsing irregularities.
Usage (cli):
$ ss | jc --ss
@@ -23,21 +20,29 @@ field names
[
{
"netid": string,
"state": string,
"recv_q": integer,
"send_q": integer,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"peer_address": string,
"peer_port": string,
"peer_port_num": integer,
"interface": string,
"link_layer" string,
"channel": string,
"path": string,
"pid": integer
"netid": string,
"state": string,
"recv_q": integer,
"send_q": integer,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"peer_address": string,
"peer_port": string,
"peer_port_num": integer,
"interface": string,
"link_layer" string,
"channel": string,
"path": string,
"pid": integer,
"opts": {
"process_id": {
"<process_id>": {
"user": string,
"file_descriptor": string
}
}
}
}
]
@@ -275,13 +280,15 @@ Examples:
}
]
"""
import re
import ast
import string
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.6'
version = '1.7'
description = '`ss` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -324,6 +331,57 @@ def _process(proc_data):
return proc_data
def _parse_opts(proc_data):
""" Process extra options -e, -o, -p
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
Structured data dictionary for extra/optional headerless options.
"""
o_field = proc_data.split(' ')
opts = {}
for item in o_field:
# -e option:
item = re.sub(
'uid', 'uid_number',
re.sub('sk', 'cookie', re.sub('ino', 'inode_number', item)))
if ":" in item:
key, val = item.split(':')
# -o option
if key == "timer":
val = val.replace('(', '[').replace(')', ']')
val = ast.literal_eval(re.sub(r'([a-z0-9\.]+)', '"\\1"', val))
val = {
'timer_name': val[0],
'expire_time': val[1],
'retrans': val[2]
}
opts[key] = val
# -p option
if key == "users":
key = 'process_id'
val = val.replace('(', '[').replace(')', ']')
val = ast.literal_eval(re.sub(r'([a-z]+=[0-9]+)', '"\\1"', val))
data = {}
for rec in val:
params = {}
params['user'] = rec[0]
for i in [x for x in rec if '=' in x]:
k, v = i.split('=')
params[k] = v
data.update({
params['pid']: {
'user': params['user'],
'file_descriptor': params['fd']
}
})
val = data
opts[key] = val
return opts
def parse(data, raw=False, quiet=False):
"""
@@ -357,15 +415,20 @@ def parse(data, raw=False, quiet=False):
header_text = header_text.replace('-', '_')
header_list = header_text.split()
extra_opts = False
for entry in cleandata[1:]:
output_line = {}
if entry[0] not in string.whitespace:
# fix weird ss bug where first two columns have no space between them sometimes
entry = entry[:5] + ' ' + entry[5:]
entry = entry[:5] + ' ' + entry[5:]
entry_list = entry.split()
entry_list = re.split(r'[ ]{1,}',entry.strip())
if len(entry_list) > len(header_list) or extra_opts == True:
entry_list = re.split(r'[ ]{2,}',entry.strip())
extra_opts = True
if entry_list[0] in contains_colon and ':' in entry_list[4]:
l_field = entry_list[4].rsplit(':', maxsplit=1)
@@ -381,6 +444,10 @@ def parse(data, raw=False, quiet=False):
entry_list[6] = p_address
entry_list.insert(7, p_port)
if re.search(r'ino:|uid:|sk:|users:|timer:',entry_list[-1]):
header_list.append('opts')
entry_list[-1] = _parse_opts(entry_list[-1])
output_line = dict(zip(header_list, entry_list))
# some post processing to pull out fields: interface, link_layer, path, pid, channel

View File

@@ -171,7 +171,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.12'
version = '1.13'
description = '`stat` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -238,112 +238,115 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
# linux output
if cleandata[0].startswith(' File: '):
# stats output contains 8 lines
for line in cleandata:
# line #1
if line.find('File:') == 2:
output_line = {}
line_list = line.split(maxsplit=1)
output_line['file'] = line_list[1]
# populate link_to field if -> found
if ' -> ' in output_line['file']:
filename = output_line['file'].split(' -> ')[0].strip('\u2018').rstrip('\u2019')
link = output_line['file'].split(' -> ')[1].strip('\u2018').rstrip('\u2019')
output_line['file'] = filename
output_line['link_to'] = link
else:
filename = output_line['file'].split(' -> ')[0].strip('\u2018').rstrip('\u2019')
output_line['file'] = filename
continue
# line #2
if line.find('Size:') == 2:
line_list = line.split(maxsplit=7)
output_line['size'] = line_list[1]
output_line['blocks'] = line_list[3]
output_line['io_blocks'] = line_list[6]
output_line['type'] = line_list[7]
continue
# line #3
if line.startswith('Device:'):
line_list = line.split()
output_line['device'] = line_list[1]
output_line['inode'] = line_list[3]
output_line['links'] = line_list[5]
continue
# line #4
if line.startswith('Access: ('):
line = line.replace('(', ' ').replace(')', ' ').replace('/', ' ')
line_list = line.split()
output_line['access'] = line_list[1]
output_line['flags'] = line_list[2]
output_line['uid'] = line_list[4]
output_line['user'] = line_list[5]
output_line['gid'] = line_list[7]
output_line['group'] = line_list[8]
continue
# line #5
if line.startswith('Access: 2'):
line_list = line.split(maxsplit=1)
output_line['access_time'] = line_list[1]
continue
# line #6
if line.startswith('Modify:'):
line_list = line.split(maxsplit=1)
output_line['modify_time'] = line_list[1]
continue
# line #7
if line.startswith('Change:'):
line_list = line.split(maxsplit=1)
output_line['change_time'] = line_list[1]
continue
# line #8
if line.find('Birth:') == 1:
line_list = line.split(maxsplit=1)
output_line['birth_time'] = line_list[1]
raw_output.append(output_line)
continue
# FreeBSD/OSX output
else:
for line in cleandata:
value = shlex.split(line)
output_line = {
'file': ' '.join(value[15:]),
'unix_device': value[0],
'inode': value[1],
'flags': value[2],
'links': value[3],
'user': value[4],
'group': value[5],
'rdev': value[6],
'size': value[7],
'access_time': value[8],
'modify_time': value[9],
'change_time': value[10],
'birth_time': value[11],
'block_size': value[12],
'blocks': value[13],
'unix_flags': value[14]
}
raw_output.append(output_line)
if raw:
if not jc.utils.has_data(data):
return raw_output
# linux output
if cleandata[0].startswith(' File: '):
output_line = {}
# stats output contains 8 lines
for line in cleandata:
# line #1
if line.find('File:') == 2:
if output_line: # Reached a new file stat info.
raw_output.append(output_line)
output_line = {}
line_list = line.split(maxsplit=1)
output_line['file'] = line_list[1]
# populate link_to field if -> found
if ' -> ' in output_line['file']:
filename = output_line['file'].split(' -> ')[0].strip('\u2018').rstrip('\u2019')
link = output_line['file'].split(' -> ')[1].strip('\u2018').rstrip('\u2019')
output_line['file'] = filename
output_line['link_to'] = link
else:
filename = output_line['file'].split(' -> ')[0].strip('\u2018').rstrip('\u2019')
output_line['file'] = filename
continue
# line #2
if line.startswith(' Size:'):
line_list = line.split(maxsplit=7)
output_line['size'] = line_list[1]
output_line['blocks'] = line_list[3]
output_line['io_blocks'] = line_list[6]
output_line['type'] = line_list[7]
continue
# line #3
if line.startswith('Device:'):
line_list = line.split()
output_line['device'] = line_list[1]
output_line['inode'] = line_list[3]
output_line['links'] = line_list[5]
continue
# line #4
if line.startswith('Access: ('):
line = line.replace('(', ' ').replace(')', ' ').replace('/', ' ')
line_list = line.split()
output_line['access'] = line_list[1]
output_line['flags'] = line_list[2]
output_line['uid'] = line_list[4]
output_line['user'] = line_list[5]
output_line['gid'] = line_list[7]
output_line['group'] = line_list[8]
continue
# line #5
if line.startswith('Access: 2'):
line_list = line.split(maxsplit=1)
output_line['access_time'] = line_list[1]
continue
# line #6
if line.startswith('Modify:'):
line_list = line.split(maxsplit=1)
output_line['modify_time'] = line_list[1]
continue
# line #7
if line.startswith('Change:'):
line_list = line.split(maxsplit=1)
output_line['change_time'] = line_list[1]
continue
# line #8
if line.startswith(' Birth:'):
line_list = line.split(maxsplit=1)
output_line['birth_time'] = line_list[1]
continue
if output_line:
raw_output.append(output_line)
# FreeBSD/OSX output
else:
return _process(raw_output)
for line in cleandata:
value = shlex.split(line)
output_line = {
'file': ' '.join(value[15:]),
'unix_device': value[0],
'inode': value[1],
'flags': value[2],
'links': value[3],
'user': value[4],
'group': value[5],
'rdev': value[6],
'size': value[7],
'access_time': value[8],
'modify_time': value[9],
'change_time': value[10],
'birth_time': value[11],
'block_size': value[12],
'blocks': value[13],
'unix_flags': value[14]
}
raw_output.append(output_line)
return raw_output if raw else _process(raw_output)

View File

@@ -1,5 +1,7 @@
"""jc - JSON Convert `timedatectl` command output parser
Also supports the `timesync-status` option.
The `epoch_utc` calculated timestamp field is timezone-aware and is only
available if the `universal_time` field is available.
@@ -29,7 +31,24 @@ Schema:
"system_clock_synchronized": boolean,
"systemd-timesyncd.service_active": boolean,
"rtc_in_local_tz": boolean,
"dst_active": boolean
"dst_active": boolean,
"server": string,
"poll_interval": string,
"leap": string,
"version": integer,
"stratum": integer,
"reference": string,
"precision": string,
"root_distance": string,
"offset": float,
"offset_unit": string,
"delay": float,
"delay_unit": string,
"jitter": float,
"jitter_unit": string,
"packet_count": integer,
"frequency": float,
"frequency_unit": string
}
Examples:
@@ -64,7 +83,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.7'
version = '1.8'
description = '`timedatectl status` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -90,11 +109,26 @@ def _process(proc_data):
"""
bool_list = {'ntp_enabled', 'ntp_synchronized', 'rtc_in_local_tz', 'dst_active',
'system_clock_synchronized', 'systemd-timesyncd.service_active'}
int_list = {'version', 'stratum', 'packet_count'}
float_list = {'offset', 'delay', 'jitter', 'frequency'}
for key in ['offset', 'delay', 'jitter']:
if key in proc_data:
proc_data[key + '_unit'] = proc_data[key][-2:]
if 'frequency' in proc_data:
proc_data['frequency_unit'] = proc_data['frequency'][-3:]
for key in proc_data:
if key in bool_list:
proc_data[key] = jc.utils.convert_to_bool(proc_data[key])
if key in int_list:
proc_data[key] = jc.utils.convert_to_int(proc_data[key])
if key in float_list:
proc_data[key] = jc.utils.convert_to_float(proc_data[key])
if 'universal_time' in proc_data:
ts = jc.utils.timestamp(proc_data['universal_time'], format_hint=(7300,))
proc_data['epoch_utc'] = ts.utc
@@ -120,17 +154,27 @@ def parse(data, raw=False, quiet=False):
jc.utils.input_type_check(data)
raw_output = {}
valid_fields = {
'local time', 'universal time', 'rtc time', 'time zone', 'ntp enabled',
'ntp synchronized', 'rtc in local tz', 'dst active',
'system clock synchronized', 'ntp service',
'systemd-timesyncd.service active', 'server', 'poll interval', 'leap',
'version', 'stratum', 'reference', 'precision', 'root distance',
'offset', 'delay', 'jitter', 'packet count', 'frequency'
}
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
linedata = line.split(':', maxsplit=1)
raw_output[linedata[0].strip().lower().replace(' ', '_')] = linedata[1].strip()
try:
key, val = line.split(':', maxsplit=1)
key = key.lower().strip()
val = val.strip()
except ValueError:
continue
if linedata[0].strip() == 'DST active':
break
if key in valid_fields:
keyname = key.replace(' ', '_')
raw_output[keyname] = val
if raw:
return raw_output
else:
return _process(raw_output)
return raw_output if raw else _process(raw_output)

View File

@@ -138,7 +138,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.2'
version = '1.3'
description = '`ufw app info [application]` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -289,6 +289,7 @@ def parse(data, raw=False, quiet=False):
if line.startswith('--'):
if item_obj:
raw_output.append(item_obj)
ports = False
item_obj = {}
continue

256
jc/parsers/veracrypt.py Normal file
View File

@@ -0,0 +1,256 @@
"""jc - JSON Convert `veracrypt` command output parser
Supports the following `veracrypt` subcommands:
- `veracrypt --text --list`
- `veracrypt --text --list --verbose`
- `veracrypt --text --volume-properties <volume>`
Usage (cli):
$ veracrypt --text --list | jc --veracrypt
or
$ jc veracrypt --text --list
Usage (module):
import jc
result = jc.parse('veracrypt', veracrypt_command_output)
Schema:
Volume:
[
{
"slot": integer,
"path": string,
"device": string,
"mountpoint": string,
"size": string,
"type": string,
"readonly": string,
"hidden_protected": string,
"encryption_algo": string,
"pk_size": string,
"sk_size": string,
"block_size": string,
"mode": string,
"prf": string,
"format_version": integer,
"backup_header": string
}
]
Examples:
$ veracrypt --text --list | jc --veracrypt -p
[
{
"slot": 1,
"path": "/dev/sdb1",
"device": "/dev/mapper/veracrypt1",
"mountpoint": "/home/bob/mount/encrypt/sdb1"
}
]
$ veracrypt --text --list --verbose | jc --veracrypt -p
[
{
"slot": 1,
"path": "/dev/sdb1",
"device": "/dev/mapper/veracrypt1",
"mountpoint": "/home/bob/mount/encrypt/sdb1",
"size": "522 MiB",
"type": "Normal",
"readonly": "No",
"hidden_protected": "No",
"encryption_algo": "AES",
"pk_size": "256 bits",
"sk_size": "256 bits",
"block_size": "128 bits",
"mode": "XTS",
"prf": "HMAC-SHA-512",
"format_version": 2,
"backup_header": "Yes"
}
]
"""
import re
from typing import List, Dict, Optional, Any
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`veracrypt` command parser'
author = 'Jake Ob'
author_email = 'iakopap at gmail.com'
compatible = ["linux"]
magic_commands = ["veracrypt"]
tags = ['command']
__version__ = info.version
try:
from typing import TypedDict
Volume = TypedDict(
"Volume",
{
"slot": int,
"path": str,
"device": str,
"mountpoint": str,
"size": str,
"type": str,
"readonly": str,
"hidden_protected": str,
"encryption_algo": str,
"pk_size": str,
"sk_size": str,
"block_size": str,
"mode": str,
"prf": str,
"format_version": int,
"backup_header": str
},
)
except ImportError:
Volume = Dict[str, Any] # type: ignore
_volume_line_pattern = r"(?P<slot>[0-9]+): (?P<path>.+?) (?P<device>.+?) (?P<mountpoint>.*)"
_volume_verbose_pattern = (
r"(Slot:\s(?P<slot>.+)"
+ r"|Volume:\s(?P<path>.+)"
+ r"|Virtual\sDevice:\s(?P<device>.+)"
+ r"|Mount\sDirectory:\s(?P<mountpoint>.+)"
+ r"|Size:\s(?P<size>.+)"
+ r"|Type:\s(?P<type>.+)"
+ r"|Read-Only:\s(?P<readonly>.+)"
+ r"|Hidden\sVolume Protected:\s(?P<hidden_protected>.+)"
+ r"|Encryption\sAlgorithm:\s(?P<encryption_algo>.+)"
+ r"|Primary\sKey\sSize:\s(?P<pk_size>.+)"
+ r"|Secondary\sKey\sSize\s.*:\s(?P<sk_size>.+)"
+ r"|Block\sSize:\s(?P<block_size>.+)"
+ r"|Mode\sof\sOperation:\s(?P<mode>.+)"
+ r"|PKCS-5\sPRF:\s(?P<prf>.+)"
+ r"|Volume\sFormat\sVersion:\s(?P<format_version>.+)"
+ r"|Embedded\sBackup\sHeader:\s(?P<backup_header>.+))"
)
def _parse_volume(next_lines: List[str]) -> Optional[Volume]:
next_line = next_lines.pop()
result = re.match(_volume_line_pattern, next_line)
# Parse and return the volume given as a single line (veracrypt -t --list)
if result:
matches = result.groupdict()
volume: Volume = { # type: ignore
"slot": int(matches["slot"]),
"path": matches["path"],
"device": matches["device"],
"mountpoint": matches["mountpoint"],
}
return volume
else:
next_lines.append(next_line)
# Otherwise parse the volume given in multiple lines (veracrypt -t --list -v)
volume: Volume = {} # type: ignore
while next_lines:
next_line = next_lines.pop()
# Return when encounter an empty line
if not next_line:
return volume
result = re.match(_volume_verbose_pattern, next_line)
# Skip to the next line in case of an unknown field line
if not result:
continue
matches = result.groupdict()
if matches["slot"]:
volume["slot"] = int(matches["slot"])
elif matches["path"]:
volume["path"] = matches["path"]
elif matches["device"]:
volume["device"] = matches["device"]
elif matches["mountpoint"]:
volume["mountpoint"] = matches["mountpoint"]
elif matches["size"]:
volume["size"] = matches["size"]
elif matches["type"]:
volume["type"] = matches["type"]
elif matches["readonly"]:
volume["readonly"] = matches["readonly"]
elif matches["hidden_protected"]:
volume["hidden_protected"] = matches["hidden_protected"]
elif matches["encryption_algo"]:
volume["encryption_algo"] = matches["encryption_algo"]
elif matches["pk_size"]:
volume["pk_size"] = matches["pk_size"]
elif matches["sk_size"]:
volume["sk_size"] = matches["sk_size"]
elif matches["block_size"]:
volume["block_size"] = matches["block_size"]
elif matches["mode"]:
volume["mode"] = matches["mode"]
elif matches["prf"]:
volume["prf"] = matches["prf"]
elif matches["format_version"]:
volume["format_version"] = int(matches["format_version"])
elif matches["backup_header"]:
volume["backup_header"] = matches["backup_header"]
return volume
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.
"""
result: List = []
if jc.utils.has_data(data):
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
linedata = data.splitlines()
first_line = linedata[0]
line_mode = re.search(_volume_line_pattern, first_line)
verbose_mode = re.search(_volume_verbose_pattern, first_line)
if not line_mode and not verbose_mode:
return []
linedata.reverse()
while linedata:
volume = _parse_volume(linedata)
if volume:
result.append(volume)
else:
break
return result

View File

@@ -408,12 +408,12 @@ from collections import OrderedDict
from datetime import datetime
from typing import List, Dict, Union
import jc.utils
from jc.parsers.asn1crypto import pem, x509
from jc.parsers.asn1crypto import pem, x509, jc_global
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.1'
version = '1.2'
description = 'X.509 PEM and DER certificate file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -462,6 +462,9 @@ def _fix_objects(obj):
Recursively traverse the nested dictionary or list and convert objects
into JSON serializable types.
"""
if isinstance(obj, tuple):
obj = list(obj)
if isinstance(obj, set):
obj = sorted(list(obj))
@@ -501,6 +504,10 @@ def _fix_objects(obj):
obj.update({k: v})
continue
if isinstance(v, tuple):
v = list(v)
obj.update({k: v})
if isinstance(v, set):
v = sorted(list(v))
obj.update({k: v})
@@ -548,6 +555,7 @@ def parse(
List of Dictionaries. 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: List = []

318
jc/parsers/x509_csr.py Normal file
View File

@@ -0,0 +1,318 @@
"""jc - JSON Convert X.509 Certificate Request format file parser
This parser will convert DER and PEM encoded X.509 certificate request files.
Usage (cli):
$ cat certificateRequest.pem | jc --x509-csr
Usage (module):
import jc
result = jc.parse('x509_csr', x509_csr_file_output)
Schema:
[
{
"certification_request_info": {
"version": string,
"serial_number": string, # [0]
"serial_number_str": string,
"signature": {
"algorithm": string,
"parameters": string/null,
},
"issuer": {
"country_name": string,
"state_or_province_name" string,
"locality_name": string,
"organization_name": array/string,
"organizational_unit_name": array/string,
"common_name": string,
"email_address": string,
"serial_number": string, # [0]
"serial_number_str": string
},
"validity": {
"not_before": integer, # [1]
"not_after": integer, # [1]
"not_before_iso": string,
"not_after_iso": string
},
"subject": {
"country_name": string,
"state_or_province_name": string,
"locality_name": string,
"organization_name": array/string,
"organizational_unit_name": array/string,
"common_name": string,
"email_address": string,
"serial_number": string, # [0]
"serial_number_str": string
},
"subject_public_key_info": {
"algorithm": {
"algorithm": string,
"parameters": string/null,
},
"public_key": {
"modulus": string, # [0]
"public_exponent": integer
}
},
"issuer_unique_id": string/null,
"subject_unique_id": string/null,
"extensions": [
{
"extn_id": string,
"critical": boolean,
"extn_value": array/object/string/integer # [2]
}
]
},
"signature_algorithm": {
"algorithm": string,
"parameters": string/null
},
"signature_value": 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 server.csr| jc --x509-csr -p
[
{
"certification_request_info": {
"version": "v1",
"subject": {
"common_name": "myserver.for.example"
},
"subject_pk_info": {
"algorithm": {
"algorithm": "ec",
"parameters": "secp256r1"
},
"public_key": "04:40:33:c0:91:8f:e9:46:ea:d0:dc:d0:f9:63:2..."
},
"attributes": [
{
"type": "extension_request",
"values": [
[
{
"extn_id": "extended_key_usage",
"critical": false,
"extn_value": [
"server_auth"
]
},
{
"extn_id": "subject_alt_name",
"critical": false,
"extn_value": [
"myserver.for.example"
]
}
]
]
}
]
},
"signature_algorithm": {
"algorithm": "sha384_ecdsa",
"parameters": null
},
"signature": "30:45:02:20:77:ac:5b:51:bf:c5:f5:43:02:52:ae:66:..."
}
]
$ openssl req -in server.csr | jc --x509-csr -p
[
{
"certification_request_info": {
"version": "v1",
"subject": {
"common_name": "myserver.for.example"
},
"subject_pk_info": {
"algorithm": {
"algorithm": "ec",
"parameters": "secp256r1"
},
"public_key": "04:40:33:c0:91:8f:e9:46:ea:d0:dc:d0:f9:63:2..."
},
"attributes": [
{
"type": "extension_request",
"values": [
[
{
"extn_id": "extended_key_usage",
"critical": false,
"extn_value": [
"server_auth"
]
},
{
"extn_id": "subject_alt_name",
"critical": false,
"extn_value": [
"myserver.for.example"
]
}
]
]
}
]
},
"signature_algorithm": {
"algorithm": "sha384_ecdsa",
"parameters": null
},
"signature": "30:45:02:20:77:ac:5b:51:bf:c5:f5:43:02:52:ae:66:..."
}
]
"""
# 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
from jc.parsers.x509_cert import _fix_objects, _process
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = 'X.509 PEM and DER certificate request 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
) -> List[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:
List of Dictionaries. 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: List = []
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
certs = []
if pem.detect(der_bytes):
for type_name, headers, der_bytes in pem.unarmor(der_bytes, multiple=True):
if type_name == 'CERTIFICATE REQUEST' or type_name == 'NEW CERTIFICATE REQUEST':
certs.append(csr.CertificationRequest.load(der_bytes))
else:
certs.append(csr.CertificationRequest.load(der_bytes))
raw_output = [_fix_objects(cert.native) for cert in certs]
return raw_output if raw else _process(raw_output)

View File

@@ -138,7 +138,7 @@ from jc.parsers.kv import parse as kv_parse
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
version = '1.1'
description = '`zpool status` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -233,7 +233,7 @@ def parse(
continue
# preserve indentation in continuation lines
if line.startswith(' '):
if line.startswith(' ') or line.startswith('\t'):
pool_str += line + '\n'
continue

View File

@@ -151,8 +151,9 @@ def compatibility(mod_name: str, compatible: List[str], quiet: bool = False) ->
mod = mod_name.split('.')[-1]
compat_list = ', '.join(compatible)
warning_message([
f'{mod} parser is not compatible with your OS ({sys.platform}).',
f'Compatible platforms: {compat_list}'
f'`{mod}` command output from this OS ({sys.platform}) is not supported.',
f'`{mod}` command output from the following platforms is supported: {compat_list}',
'Disregard this warning if you are processing output that came from a supported platform. (Use the -q option to suppress this warning)'
])
@@ -408,6 +409,7 @@ class timestamp:
{'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': 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
@@ -504,6 +506,7 @@ class timestamp:
or '+00:00' in data \
or '-00:00' in data:
utc_tz = True
data = data.replace('+00:00', '+0000') # fix for python 3.6
# normalize the timezone by taking out any timezone reference, except UTC
cleandata = data.replace('(', '').replace(')', '')

View File

@@ -1,4 +1,4 @@
.TH jc 1 2023-02-27 1.23.0 "JSON Convert"
.TH jc 1 2023-07-30 1.23.4 "JSON Convert"
.SH NAME
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types,
and strings
@@ -72,6 +72,11 @@ multi-line ASCII and Unicode table parser
\fB--blkid\fP
`blkid` command parser
.TP
.B
\fB--bluetoothctl\fP
`bluetoothctl` command parser
.TP
.B
\fB--cbt\fP
@@ -87,6 +92,11 @@ CEF string parser
\fB--cef-s\fP
CEF string streaming parser
.TP
.B
\fB--certbot\fP
`certbot` command parser
.TP
.B
\fB--chage\fP
@@ -182,6 +192,11 @@ Email Address string parser
\fB--file\fP
`file` command parser
.TP
.B
\fB--find\fP
`find` command parser
.TP
.B
\fB--findmnt\fP
@@ -297,6 +312,11 @@ IPv4 and IPv6 Address string parser
\fB--iptables\fP
`iptables` command parser
.TP
.B
\fB--ip-route\fP
`ip route` command parser
.TP
.B
\fB--iso-datetime\fP
@@ -347,6 +367,11 @@ Key/Value file and string parser
\fB--ls-s\fP
`ls` command streaming parser
.TP
.B
\fB--lsattr\fP
`lsattr` command parser
.TP
.B
\fB--lsblk\fP
@@ -677,6 +702,11 @@ PLIST file parser
\fB--proc-net-route\fP
`/proc/net/route` file parser
.TP
.B
\fB--proc-net-tcp\fP
`/proc/net/tcp` and `/proc/net/tcp6` file parser
.TP
.B
\fB--proc-net-unix\fP
@@ -732,6 +762,11 @@ PLIST file parser
\fB--ps\fP
`ps` command parser
.TP
.B
\fB--resolve-conf\fP
`/etc/resolve.conf` file parser
.TP
.B
\fB--route\fP
@@ -767,6 +802,11 @@ Semantic Version string parser
\fB--shadow\fP
`/etc/shadow` file parser
.TP
.B
\fB--srt\fP
SRT file parser
.TP
.B
\fB--ss\fP
@@ -932,6 +972,11 @@ URL string parser
\fB--ver\fP
Version string parser
.TP
.B
\fB--veracrypt\fP
`veracrypt` command parser
.TP
.B
\fB--vmstat\fP
@@ -962,6 +1007,11 @@ Version string parser
\fB--x509-cert\fP
X.509 PEM and DER certificate file parser
.TP
.B
\fB--x509-csr\fP
X.509 PEM and DER certificate request file parser
.TP
.B
\fB--xml\fP
@@ -1316,8 +1366,8 @@ etc...
Note: Unbuffered output can be slower for large data streams.
.RE
.SH CUSTOM PARSERS
Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your
.SH PARSER PLUGINS
Parser plugins may be placed in a \fBjc/jcparsers\fP folder in your
local "App data directory":
.RS
@@ -1328,11 +1378,13 @@ local "App data directory":
.fi
.RE
Local parser plugins are standard python module files. Use the
Parser plugins are standard python module files. Use the
\fBjc/parsers/foo.py\fP or \fBjc/parsers/foo_s.py\fP (streaming) parser as a
template and simply place a \fB.py\fP file in the \fBjcparsers\fP subfolder.
Any dependencies can be placed in the \fBjc\fP folder above \fBjcparsers\fP
and can be imported in the parser code.
Local plugin filenames must be valid python module names and therefore must
Parser plugin filenames must be valid python module names and therefore must
start with a letter and consist entirely of alphanumerics and underscores. Local
plugins may override default parsers.

View File

@@ -1,2 +1,2 @@
[metadata]
license_file = LICENSE.md
license_files = LICENSE.md

View File

@@ -5,7 +5,7 @@ with open('README.md', 'r') as f:
setuptools.setup(
name='jc',
version='1.23.0',
version='1.23.4',
author='Kelly Brazil',
author_email='kellyjonbrazil@gmail.com',
description='Converts the output of popular command-line tools and file-types to JSON.',
@@ -20,6 +20,7 @@ setuptools.setup(
python_requires='>=3.6',
url='https://github.com/kellyjonbrazil/jc',
packages=setuptools.find_packages(exclude=['*.tests', '*.tests.*', 'tests.*', 'tests']),
package_data={'jc': ['py.typed']},
entry_points={
'console_scripts': [
'jc=jc.cli:main'

View File

@@ -366,8 +366,8 @@ etc...
Note: Unbuffered output can be slower for large data streams.
.RE
.SH CUSTOM PARSERS
Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your
.SH PARSER PLUGINS
Parser plugins may be placed in a \fBjc/jcparsers\fP folder in your
local "App data directory":
.RS
@@ -378,11 +378,13 @@ local "App data directory":
.fi
.RE
Local parser plugins are standard python module files. Use the
Parser plugins are standard python module files. Use the
\fBjc/parsers/foo.py\fP or \fBjc/parsers/foo_s.py\fP (streaming) parser as a
template and simply place a \fB.py\fP file in the \fBjcparsers\fP subfolder.
Any dependencies can be placed in the \fBjc\fP folder above \fBjcparsers\fP
and can be imported in the parser code.
Local plugin filenames must be valid python module names and therefore must
Parser plugin filenames must be valid python module names and therefore must
start with a letter and consist entirely of alphanumerics and underscores. Local
plugins may override default parsers.

View File

@@ -5,11 +5,13 @@
> Try the `jc` [web demo](https://jc-web.onrender.com/) and [REST API](https://github.com/kellyjonbrazil/jc-restapi)
> JC is [now available](https://galaxy.ansible.com/community/general) as an
> `jc` is [now available](https://galaxy.ansible.com/community/general) as an
Ansible filter plugin in the `community.general` collection. See this
[blog post](https://blog.kellybrazil.com/2020/08/30/parsing-command-output-in-ansible-with-jc/)
for an example.
> Looking for something like `jc` but lower-level? Check out [regex2json](https://gitlab.com/tozd/regex2json).
# JC
JSON Convert
@@ -393,20 +395,22 @@ for item in result:
print(item["filename"])
```
### Custom Parsers
Custom local parser plugins may be placed in a `jc/jcparsers` folder in your
local **"App data directory"**:
### Parser Plugins
Parser plugins may be placed in a `jc/jcparsers` folder in your local
**"App data directory"**:
- Linux/unix: `$HOME/.local/share/jc/jcparsers`
- macOS: `$HOME/Library/Application Support/jc/jcparsers`
- Windows: `$LOCALAPPDATA\jc\jc\jcparsers`
Local parser plugins are standard python module files. Use the
Parser plugins are standard python module files. Use the
[`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py)
or [`jc/parsers/foo_s.py (streaming)`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo_s.py)
parser as a template and simply place a `.py` file in the `jcparsers` subfolder.
Any dependencies can be placed in the `jc` folder above `jcparsers` and can
be imported in the parser code.
Local plugin filenames must be valid python module names and therefore must
Parser plugin filenames must be valid python module names and therefore must
start with a letter and consist entirely of alphanumerics and underscores.
Local plugins may override default parsers.
@@ -464,7 +468,7 @@ they are run on an unsupported platform. To see all parser information,
including compatibility, run `jc -ap`.
You may still use a parser on an unsupported platform - for example, you may
want to parse a file with linux `lsof` output on an macOS or Windows laptop. In
want to parse a file with linux `lsof` output on a macOS or Windows laptop. In
that case you can suppress the warning message with the `-q` cli option or the
`quiet=True` function parameter in `parse()`:

13
tests/fixtures/centos-7.7/find.json vendored Normal file
View File

@@ -0,0 +1,13 @@
[{"path": null, "node": "."},
{"path":".","node":null},
{"path": ".","node": "jc"},
{"path": "./jc","node": "tests"},
{"path": "./jc/tests","node": "test_find.py"},
{"path": "./jc/tests","node": "test_history.py"},
{"path": "./jc/tests","node": "test_hosts.py"},
{"path": "./jc","node": "anotherdirectory"},
{"path": null,"node": null,"error": "find: './inaccessible': Permission denied"},
{"path": "./jc","node": "directory2"},
{"path": "./jc/directory2","node": "file.txt"},
{"path": "./jc/directory2","node": "file2.txt"},
{"path": ".","node": "newfile.txt"}]

13
tests/fixtures/centos-7.7/find.out vendored Normal file
View File

@@ -0,0 +1,13 @@
.
./
./jc
./jc/tests
./jc/tests/test_find.py
./jc/tests/test_history.py
./jc/tests/test_hosts.py
./jc/anotherdirectory
find: './inaccessible': Permission denied
./jc/directory2
./jc/directory2/file.txt
./jc/directory2/file2.txt
./newfile.txt

31
tests/fixtures/centos-7.7/ip_route.json vendored Normal file
View File

@@ -0,0 +1,31 @@
[
{
"ip": "default",
"via": "10.0.2.2",
"dev": "enp0s3",
"proto": "dhcp",
"metric": 100
},
{
"ip": "10.0.2.0/24",
"dev": "enp0s3",
"proto": "kernel",
"scope": "link",
"src": "10.0.2.15",
"metric": 100
},
{
"ip": "169.254.0.0/16",
"dev": "enp0s3",
"scope": "link",
"metric": 1000
},
{
"ip": "172.17.0.0/16",
"dev": "docker0",
"proto": "kernel",
"scope": "link",
"src": "172.17.0.1",
"status": "linkdown"
}
]

View File

@@ -0,0 +1,4 @@
default via 10.0.2.2 dev enp0s3 proto dhcp metric 100
10.0.2.0/24 dev enp0s3 proto kernel scope link src 10.0.2.15 metric 100
169.254.0.0/16 dev enp0s3 scope link metric 1000
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown

View File

@@ -0,0 +1 @@
[{"user":"root","tty":"pts/0","hostname":"192.168.255.1","login":"Mon Jun 19 14:18:13 2023","logout":"still logged in","login_epoch":1687209493}, {"user":"mark","tty":"pts/0","hostname":"192.168.255.1","login":"Mon Jun 19 14:15:57 2023","logout":"Mon Jun 19 14:18:00 2023","duration":"00:02","login_epoch":1687209357,"logout_epoch":1687209480,"duration_seconds":123}, {"user":"mark","tty":"pts/0","hostname":"192.168.255.1","login":"Mon Jun 19 14:15:45 2023","logout":"Mon Jun 19 14:15:52 2023","duration":"00:00","login_epoch":1687209345,"logout_epoch":1687209352,"duration_seconds":7}, {"user":"mark","tty":"tty1","hostname":"0.0.0.0","login":"Mon Jun 19 16:59:57 2023","logout":"still logged in","login_epoch":1687219197}, {"user":"runlevel","tty":"(to lvl 3)","hostname":"0.0.0.0","login":"Mon Jun 19 16:59:39 2023","logout":"Mon Jun 19 14:35:00 2023","duration":"-2:-24","login_epoch":1687219179,"logout_epoch":1687210500,"duration_seconds":-8679}, {"user":"reboot","tty":"system boot","hostname":"0.0.0.0","login":"Mon Jun 19 16:59:20 2023","logout":"Mon Jun 19 14:35:00 2023","duration":"-2:-24","login_epoch":1687219160,"logout_epoch":1687210500,"duration_seconds":-8660},{"user": "shutdown","tty": "system down","hostname": "0.0.0.0","login": "Fri Apr 14 13:46:46 2023","logout": "Fri Apr 14 13:47:12 2023","duration": "00:00","login_epoch": 1681505206,"logout_epoch": 1681505232,"duration_seconds": 26}]

View File

@@ -0,0 +1,9 @@
root pts/0 192.168.255.1 Mon Jun 19 14:18:13 2023 still logged in
mark pts/0 192.168.255.1 Mon Jun 19 14:15:57 2023 - Mon Jun 19 14:18:00 2023 (00:02)
mark pts/0 192.168.255.1 Mon Jun 19 14:15:45 2023 - Mon Jun 19 14:15:52 2023 (00:00)
mark tty1 0.0.0.0 Mon Jun 19 16:59:57 2023 still logged in
runlevel (to lvl 3) 0.0.0.0 Mon Jun 19 16:59:39 2023 - Mon Jun 19 14:35:00 2023 (-2:-24)
reboot system boot 0.0.0.0 Mon Jun 19 16:59:20 2023 - Mon Jun 19 14:35:00 2023 (-2:-24)
shutdown system down 0.0.0.0 Fri Apr 14 13:46:46 2023 - Fri Apr 14 13:47:12 2023 (00:00)
wtmp begins Mon Jun 19 16:59:20 2023

View File

@@ -0,0 +1 @@
[{"type":"reply","destination_ip":"100.68.105.124","sent_bytes":56,"pattern":null,"timestamp":null,"response_bytes":64,"response_ip":"100.68.105.124","icmp_seq":1,"ttl":64,"time_ms":0.04,"duplicate":false},{"type":"summary","destination_ip":"100.68.105.124","sent_bytes":56,"pattern":null,"packets_transmitted":1,"packets_received":1,"packet_loss_percent":0.0,"duplicates":0,"time_ms":0.0,"round_trip_ms_min":0.04,"round_trip_ms_avg":0.04,"round_trip_ms_max":0.04,"round_trip_ms_stddev":0.0}]

View File

@@ -0,0 +1 @@
{"destination_ip":"100.68.105.124","data_bytes":56,"pattern":null,"packets_transmitted":1,"packets_received":1,"packet_loss_percent":0.0,"duplicates":0,"time_ms":0.0,"round_trip_ms_min":0.04,"round_trip_ms_avg":0.04,"round_trip_ms_max":0.04,"round_trip_ms_stddev":0.0,"responses":[{"type":"reply","timestamp":null,"bytes":64,"response_ip":"100.68.105.124","icmp_seq":1,"ttl":64,"time_ms":0.04,"duplicate":false}]}

View File

@@ -0,0 +1,6 @@
PING (100.68.105.124) 56(84) bytes of data.
64 bytes from 100.68.105.124 (100.68.105.124): icmp_seq=1 ttl=64 time=0.040 ms
--- ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.040/0.040/0.040/0.000 ms

View File

@@ -0,0 +1 @@
[{"type":"Battery","id":0,"state":"Not charging","charge_percent":100}]

View File

@@ -0,0 +1 @@
Battery 0: Not charging, 100%

View File

@@ -0,0 +1,17 @@
Controller CC:BB:AF:27:6A:E4 (public)
Name: arch
Alias: arch
Class: 0x006c010c
Powered: yes
Discoverable: no
DiscoverableTimeout: 0x000000b4
Pairable: no
UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb)
UUID: Audio Source (0000110a-0000-1000-8000-00805f9b34fb)
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: Handsfree Audio Gateway (0000111f-0000-1000-8000-00805f9b34fb)
Modalias: usb:v1D6Bp0246d0542
Discovering: no

View File

@@ -0,0 +1,20 @@
Device EB:06:EF:62:B3:19 (public)
Name: TaoTronics TT-BH026
Alias: TaoTronics TT-BH026
Class: 0x00240404
Icon: audio-headset
Paired: no
Bonded: no
Trusted: no
Blocked: no
Connected: no
LegacyPairing: no
UUID: Advanced Audio Distribu.. (0000110d-0000-1000-8000-00805f9b34fb)
UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb)
UUID: A/V Remote Control Cont.. (0000110f-0000-1000-8000-00805f9b34fb)
UUID: Handsfree (0000111e-0000-1000-8000-00805f9b34fb)
UUID: Headset (00001108-0000-1000-8000-00805f9b34fb)
UUID: Headset HS (00001131-0000-1000-8000-00805f9b34fb)
RSSI: -52
TxPower: 4

View File

@@ -0,0 +1,18 @@
Device DF:1C:C3:B4:1A:1F (random)
Name: M585/M590
Alias: M585/M590
Appearance: 0x03c2
Icon: input-mouse
Paired: yes
Bonded: yes
Trusted: no
Blocked: no
Connected: no
LegacyPairing: no
UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb)
UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
UUID: Device Information (0000180a-0000-1000-8000-00805f9b34fb)
UUID: Battery Service (0000180f-0000-1000-8000-00805f9b34fb)
UUID: Human Interface Device (00001812-0000-1000-8000-00805f9b34fb)
UUID: Vendor specific (00010000-0000-1000-8000-011f2000046d)
Modalias: usb:v046DpB01Bd0011

View File

@@ -0,0 +1 @@
{"account":{"server":"https://acme-staging-v02.api.letsencrypt.org/directory","url":"https://acme-staging-v02.api.letsencrypt.org/acme/acct/12345679","email":"some@example.com"}}

View File

@@ -0,0 +1,5 @@
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Account details for server https://acme-staging-v02.api.letsencrypt.org/directory:
Account URL: https://acme-staging-v02.api.letsencrypt.org/acme/acct/12345679
Email contact: some@example.com

View File

@@ -0,0 +1 @@
{"certificates":[{"name":"example.com","serial_number":"3f7axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","key_type":"RSA","domains":["example.com","www.example.com"],"expiration_date":"2023-05-11 01:33:10+00:00","validity":"63 days","certificate_path":"/etc/letsencrypt/live/example.com/fullchain.pem","private_key_path":"/etc/letsencrypt/live/example.com/privkey.pem","expiration_date_epoch":1683793990,"expiration_date_epoch_utc":1683768790,"expiration_date_iso":"2023-05-11T01:33:10+00:00"},{"name":"example.org","serial_number":"3bcyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy","key_type":"RSA","domains":["example.org","www.example.org"],"expiration_date":"2023-06-12 01:35:30+00:00","validity":"63 days","certificate_path":"/etc/letsencrypt/live/example.org/fullchain.pem","private_key_path":"/etc/letsencrypt/live/example.org/privkey.pem","expiration_date_epoch":1686558930,"expiration_date_epoch_utc":1686533730,"expiration_date_iso":"2023-06-12T01:35:30+00:00"}]}

View File

@@ -0,0 +1,20 @@
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
Certificate Name: example.com
Serial Number: 3f7axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Key Type: RSA
Domains: example.com www.example.com
Expiry Date: 2023-05-11 01:33:10+00:00 (VALID: 63 days)
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
Certificate Name: example.org
Serial Number: 3bcyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
Key Type: RSA
Domains: example.org www.example.org
Expiry Date: 2023-06-12 01:35:30+00:00 (VALID: 63 days)
Certificate Path: /etc/letsencrypt/live/example.org/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.org/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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