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

Compare commits

...

154 Commits

Author SHA1 Message Date
Kelly Brazil
de3b91a36c add -dd option 2020-06-25 07:38:39 -07:00
Kelly Brazil
ef5482c3b5 add verbose debug option 2020-06-25 07:29:28 -07:00
Kelly Brazil
d20b795137 Merge pull request #70 from kellyjonbrazil/dev
Dev v1.11.7
2020-06-22 11:28:23 -07:00
Kelly Brazil
8a134065df update fixtures for last chain fix 2020-06-22 11:23:37 -07:00
Kelly Brazil
22aee1bfa4 version bump 2020-06-22 11:23:15 -07:00
Kelly Brazil
b282820fd6 fix to include the final chain in output 2020-06-22 11:09:09 -07:00
Kelly Brazil
3ee098306d version bump 2020-06-22 10:48:51 -07:00
Kelly Brazil
09e8f379a6 iptables code optimizations 2020-06-22 10:47:34 -07:00
Kelly Brazil
69018cdb3a fix date 2020-06-14 17:39:14 -07:00
Kelly Brazil
d0d7254c6a add docstring 2020-06-14 17:23:10 -07:00
Kelly Brazil
cc0f0971d7 Improve and standardize empty data check for all parsers 2020-06-14 17:17:40 -07:00
Kelly Brazil
2af61730f0 Merge pull request #68 from kellyjonbrazil/dev
Dev v1.11.5
2020-06-12 12:34:34 -07:00
Kelly Brazil
83f41b83dc version bump 2020-06-12 12:30:19 -07:00
Kelly Brazil
1fb84fce88 fix for no data 2020-06-12 12:25:07 -07:00
Kelly Brazil
a8837e1244 remove --upgrade from pip install 2020-06-12 07:57:40 -07:00
Kelly Brazil
04d2eec558 fix for no data 2020-06-11 17:59:06 -07:00
Kelly Brazil
1b57ec92f0 fix for no data 2020-06-11 17:52:03 -07:00
Kelly Brazil
4d88595404 enhance empty data check 2020-06-11 17:16:11 -07:00
Kelly Brazil
52b1272a3a enhance empty data check 2020-06-11 17:13:45 -07:00
Kelly Brazil
d2ccad6a83 fix for no data 2020-06-11 17:09:51 -07:00
Kelly Brazil
cad6dde4ac fix for no data 2020-06-10 17:54:06 -07:00
Kelly Brazil
06811c3539 add test for no data 2020-06-10 17:41:54 -07:00
Kelly Brazil
0cb23c2b21 add fix for no data 2020-06-10 17:40:18 -07:00
Kelly Brazil
ac4688dca2 add test for no data 2020-06-10 17:35:40 -07:00
Kelly Brazil
326c3b4670 add test for no data 2020-06-10 17:34:22 -07:00
Kelly Brazil
9b29d0c268 add test for no data 2020-06-10 17:32:39 -07:00
Kelly Brazil
e0013c3871 add test for no data 2020-06-10 17:31:14 -07:00
Kelly Brazil
a75744075b add no data test 2020-06-10 17:29:41 -07:00
Kelly Brazil
525aec1a02 fix for no data 2020-06-10 17:27:46 -07:00
Kelly Brazil
0bf9a7a072 add test for no data 2020-06-10 17:22:59 -07:00
Kelly Brazil
d8f2f4c95b fix for no data 2020-06-10 17:20:09 -07:00
Kelly Brazil
35d733b44f fix for no data 2020-06-10 17:10:53 -07:00
Kelly Brazil
9179b4175c add nodata tests 2020-06-10 16:40:11 -07:00
Kelly Brazil
bb07d78c78 add nodata fix 2020-06-10 16:39:49 -07:00
Kelly Brazil
07b179cd7f Merge pull request #67 from kellyjonbrazil/Dev
Dev v1.11.4
2020-06-10 06:07:42 -07:00
Kelly Brazil
054422d837 add test for empty directory 2020-06-10 06:04:50 -07:00
Kelly Brazil
3e052d1810 version bump 2020-06-10 05:53:20 -07:00
Kelly Brazil
c8e72805cf fix error on empty directory 2020-06-10 05:51:12 -07:00
Kelly Brazil
12a80e7db0 add fedora package info 2020-06-09 15:13:53 -07:00
Kelly Brazil
ee7ff9a09d Merge pull request #66 from kellyjonbrazil/dev
Dev v1.11.3
2020-06-09 11:22:39 -07:00
Kelly Brazil
f6478fb636 version bump 2020-06-09 11:18:47 -07:00
Kelly Brazil
811a0b0495 add info regarding the local parser plugin files 2020-06-08 10:54:42 -07:00
Kelly Brazil
aeb48edf72 use $LOCALAPPDATA variable for windows 2020-06-08 10:48:58 -07:00
Kelly Brazil
b1e94f0df7 heading formatting 2020-06-08 10:44:09 -07:00
Kelly Brazil
60050e3c0f fix linux/unix directory and add note about the XDG specification followed 2020-06-08 10:42:45 -07:00
Kelly Brazil
39ef09aa5b add local parser plugin feature 2020-06-07 13:30:22 -07:00
Kelly Brazil
8377d43116 formatting 2020-06-07 13:26:03 -07:00
Kelly Brazil
54e4c447ab clean up formatting 2020-06-07 12:52:16 -07:00
Kelly Brazil
937a9fa9cf vendorize appdirs module 2020-06-07 12:41:50 -07:00
Kelly Brazil
808ff6cf0e more acknowledgments updates 2020-06-07 12:29:10 -07:00
Kelly Brazil
7f5c649a95 update acknowledgments 2020-06-07 12:23:28 -07:00
Kelly Brazil
b72727dec9 update custom parsers info 2020-06-07 12:13:40 -07:00
Kelly Brazil
3fc88bfb33 Merge pull request #65 from duelafn/local-parsers
Load custom parsers from <user_data_dir>/jc/jcparsers
2020-06-07 12:04:44 -07:00
Dean Serenevy
9f2279d586 Load custom parsers from <user_data_dir>/jc/jcparsers 2020-06-06 14:42:27 -04:00
Kelly Brazil
346a14cb9b change osx_device to unix_device 2020-05-30 20:44:14 -07:00
Kelly Brazil
dac00d17ff add nixos test 2020-05-30 20:33:50 -07:00
Kelly Brazil
9ca7cd4060 update docs 2020-05-30 20:33:39 -07:00
Kelly Brazil
aa31628970 update docs 2020-05-30 20:33:00 -07:00
Kelly Brazil
bed694fcf5 version bump 2020-05-30 20:13:21 -07:00
Kelly Brazil
4b4af69fa1 fix date 2020-05-30 20:12:51 -07:00
Kelly Brazil
9d96190a5b Merge pull request #64 from kellyjonbrazil/dev
Dev v1.11.2
2020-05-30 20:08:32 -07:00
Kelly Brazil
fa44d48c09 freebsd fixes, tests, and fixtures 2020-05-30 19:50:38 -07:00
Kelly Brazil
4ef961c278 add freebsd test and fixtures 2020-05-30 19:18:01 -07:00
Kelly Brazil
292a837d5c add tests and fixtures for freebsd12 2020-05-30 18:54:09 -07:00
Kelly Brazil
aa7b915d84 version bump 2020-05-30 18:53:46 -07:00
Kelly Brazil
c46fe73236 add last fixes for freebsd 2020-05-30 18:53:35 -07:00
Kelly Brazil
039b2c129c freebsd fixes 2020-05-30 18:42:26 -07:00
Kelly Brazil
8f2e5e4808 fix compatible logic 2020-05-30 17:46:09 -07:00
Kelly Brazil
c4da8e4f78 add nixos and freebsd to tested. update new arp fields 2020-05-30 17:05:41 -07:00
Kelly Brazil
bcab9078a4 add w parser fix 2020-05-30 17:02:09 -07:00
Kelly Brazil
b3c6c1ea92 strip whitespace in string fields and add tests 2020-05-30 17:01:59 -07:00
Kelly Brazil
a3af8662bd add permanent field 2020-05-30 16:26:07 -07:00
Kelly Brazil
35940d0bc8 add freebsd permanent and expires fields 2020-05-30 16:25:53 -07:00
Kelly Brazil
26994cdcb7 add freebsd compatibility info 2020-05-30 15:51:54 -07:00
Kelly Brazil
017159a829 add freebsd nestat tests and fixtures 2020-05-30 15:51:06 -07:00
Kelly Brazil
b4e9c85e08 fixup -T freebsd output and add whitespace stripping to parse_post 2020-05-30 15:50:45 -07:00
Kelly Brazil
189146cd84 add more ints. remove whitespace strip code and move to freebsd_osx module 2020-05-30 15:50:07 -07:00
Kelly Brazil
af34153ffa version bump 2020-05-30 15:48:56 -07:00
Kelly Brazil
bf2ff3ffbb fix compatibility search for platform names that append the version number (e.g. freebsd12) 2020-05-30 15:48:29 -07:00
Kelly Brazil
6423c9efd6 integer and float updates 2020-05-29 15:48:51 -07:00
Kelly Brazil
58ab0d4ece strip whitespace from string fields 2020-05-29 15:14:44 -07:00
Kelly Brazil
83a738bf4d update fixtures for osx_flags and osx_inode name change to unix_flags and unix_inode 2020-05-29 14:16:11 -07:00
Kelly Brazil
3640671fc6 rename module 2020-05-29 14:15:28 -07:00
Kelly Brazil
1da623b30e add items 2020-05-29 14:14:48 -07:00
Kelly Brazil
b10ca64646 change osx_inode and osx_flags to unix_inode and unix_flags. Also rename netstat_osx module to netstat_freebsd_osx 2020-05-29 14:14:37 -07:00
Kelly Brazil
2128763ee6 fix osx version from 16.4 to 14.6 2020-05-29 13:25:19 -07:00
Kelly Brazil
a27e7ed39c test updates for added route_flags_pretty and flags_pretty fields 2020-05-29 13:24:52 -07:00
Kelly Brazil
f07b7eaa47 add flags_pretty 2020-05-29 12:55:16 -07:00
Kelly Brazil
6ce18de84c add route_flags_pretty 2020-05-29 12:51:04 -07:00
Kelly Brazil
8631b756e7 add freebsd test files 2020-05-29 12:05:46 -07:00
Kelly Brazil
7414d98412 add freebsd compatibility 2020-05-29 12:05:34 -07:00
Kelly Brazil
d7b19892e8 add freebsd support for netstat -i 2020-05-29 12:04:58 -07:00
Kelly Brazil
96df396eaf formatting 2020-05-29 07:35:25 -07:00
Kelly Brazil
2f6f640317 spelling 2020-05-27 17:11:20 -07:00
Kelly Brazil
c4a0a50f3a add nix-env 2020-05-27 17:08:54 -07:00
Kelly Brazil
658f8a3842 add zypper and ports info 2020-05-27 14:58:16 -07:00
Kelly Brazil
bfb876a1e3 formatting 2020-05-27 09:52:13 -07:00
Kelly Brazil
90c34b1f4e fix dmidecode example 2020-05-26 17:31:56 -07:00
Kelly Brazil
3f9164ea77 bold formatting 2020-05-23 21:11:17 -07:00
Kelly Brazil
7fd6fecbf5 formatting 2020-05-23 21:09:49 -07:00
Kelly Brazil
8029f72363 change osx_flags from integer to string 2020-05-23 21:09:40 -07:00
Kelly Brazil
c7fdce5d3b Merge pull request #62 from kellyjonbrazil/dev
Dev v1.11.0
2020-05-22 16:37:50 -07:00
Kelly Brazil
84f48aa369 version bump 2020-05-22 16:30:22 -07:00
Kelly Brazil
2e9a0a9c12 add features 2020-05-22 16:21:55 -07:00
Kelly Brazil
c1f6f2b950 osx fixes and tests 2020-05-22 16:21:40 -07:00
Kelly Brazil
ede21bca13 add OSX support for stat 2020-05-22 16:05:04 -07:00
Kelly Brazil
8dd9a9f9cb add netstat -i tests 2020-05-22 15:12:10 -07:00
Kelly Brazil
04f92cd133 add linux support for netstat -i 2020-05-22 14:04:11 -07:00
Kelly Brazil
8be8d2393b add netstat -i support for OSX 2020-05-22 13:38:25 -07:00
Kelly Brazil
0a879681be add netstat -r to docs 2020-05-22 12:56:27 -07:00
Kelly Brazil
2ca1587a49 add linux netstat -r tests 2020-05-22 12:44:51 -07:00
Kelly Brazil
ec2cd2d708 add netstat -r support for linux 2020-05-22 12:00:26 -07:00
Kelly Brazil
5d0dbece93 add netstat -r functionality for OSX 2020-05-22 11:09:41 -07:00
Kelly Brazil
df1e4b414b remove unused folder 2020-05-22 08:00:18 -07:00
Kelly Brazil
40760991e7 update copyright date 2020-05-22 07:51:47 -07:00
Kelly Brazil
464f5f86cf update description 2020-05-22 07:50:22 -07:00
Kelly Brazil
7b09e9fccd set empty values to Null and update fixtures 2020-05-21 17:01:17 -07:00
Kelly Brazil
6cba7d4298 remove linux from description 2020-05-21 11:10:00 -07:00
Kelly Brazil
9730f62e49 fixup name field and update test fixtures 2020-05-21 09:44:28 -07:00
Kelly Brazil
e0c1c87f54 formatting 2020-05-21 09:07:01 -07:00
Kelly Brazil
931b3d2b83 formatting 2020-05-20 19:56:28 -07:00
Kelly Brazil
e5d561baee add multipath condition for osx detection 2020-05-20 17:31:51 -07:00
Kelly Brazil
2867593e7a changelog update 2020-05-20 17:28:44 -07:00
Kelly Brazil
dd52fee563 osx netstat tests and fixtures 2020-05-20 17:25:25 -07:00
Kelly Brazil
8e1f885827 fix filtered netstat views 2020-05-20 16:43:53 -07:00
Kelly Brazil
2d39a58f90 doc update 2020-05-20 16:14:03 -07:00
Kelly Brazil
9c4fa2ae26 integer conversions and icmp fix 2020-05-20 15:39:47 -07:00
Kelly Brazil
de52d84e82 fix udp state and udp46 entries 2020-05-20 12:02:32 -07:00
Kelly Brazil
ce9b55059a organize files 2020-05-20 11:24:38 -07:00
Kelly Brazil
bcd370a6a0 code cleanup 2020-05-20 08:19:45 -07:00
Kelly Brazil
c8216850ab code cleanup 2020-05-20 07:19:24 -07:00
Kelly Brazil
f5feedb90b fix comments 2020-05-19 17:38:14 -07:00
Kelly Brazil
a4371cd187 support netstat -A 2020-05-19 17:36:16 -07:00
Kelly Brazil
9d5ba4c834 formatting 2020-05-19 17:14:04 -07:00
Kelly Brazil
1639dee1bb fix parse_post 2020-05-19 17:13:03 -07:00
Kelly Brazil
9363f430f2 use list extend method to simplify code 2020-05-19 16:26:41 -07:00
Kelly Brazil
9192a09073 parse all sections 2020-05-19 16:26:04 -07:00
Kelly Brazil
b915eb9755 initial osx parser 2020-05-19 15:15:08 -07:00
Kelly Brazil
1cfcc2b592 tighten up line test logic when counting tabs 2020-05-14 09:58:16 -07:00
Kelly Brazil
7138dd02b7 cleanup variables after adding to item 2020-05-14 09:51:10 -07:00
Kelly Brazil
b4276643b7 add dmidecode tests and fixtures 2020-05-14 09:43:13 -07:00
Kelly Brazil
2ef00763bf fix first item 2020-05-14 09:33:45 -07:00
Kelly Brazil
54364928fc fix oddities like hybrid single/multiline data and items containing multiple records 2020-05-14 08:57:23 -07:00
Kelly Brazil
09b3b4932b add dmidecode output fixtures 2020-05-13 10:51:38 -07:00
Kelly Brazil
29d6670119 convert integers 2020-05-13 10:18:49 -07:00
Kelly Brazil
2f654b5f1a doc update 2020-05-13 10:13:27 -07:00
Kelly Brazil
e53b9f5992 add caveats to documentation 2020-05-13 10:12:40 -07:00
Kelly Brazil
addb234e61 add dmidecode doc 2020-05-13 09:44:20 -07:00
Kelly Brazil
76eca3b659 add dmidecode 2020-05-13 09:43:35 -07:00
Kelly Brazil
f90dec4c0e add examples to documentation 2020-05-13 09:43:23 -07:00
Kelly Brazil
8900a59d4c simplify logic by removing redundant block 2020-05-13 09:31:12 -07:00
Kelly Brazil
6685138200 fix for missing multi-line values that come immediately after a previous multi-line value 2020-05-13 09:25:02 -07:00
Kelly Brazil
4d3e65b980 fix missing values 2020-05-13 09:00:32 -07:00
Kelly Brazil
e9282bb546 add dmidecode parser 2020-05-13 08:22:52 -07:00
212 changed files with 42532 additions and 1316 deletions

1
.gitignore vendored
View File

@@ -3,5 +3,4 @@ __pycache__
dist/
build/
*.egg-info/
jc/parsers.old/
.github/

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019 Kelly Brazil
Copyright (c) 2020 Kelly Brazil
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

250
README.md
View File

@@ -4,9 +4,9 @@
# JC
JSON CLI output utility
`jc` is used to JSONify the output of many standard linux cli tools and file types for easier parsing in scripts. See the [**Parsers**](#parsers) section for supported commands and file types.
`jc` JSONifies the output of many CLI tools and file-types for easier parsing in scripts. See the [**Parsers**](#parsers) section for supported commands and file-types.
This allows further command line processing of output with tools like `jq` simply by piping commands:
This allows further command-line processing of output with tools like `jq` by piping commands:
```
$ ls -l /usr/bin | jc --ls | jq '.[] | select(.size > 50000000)'
{
@@ -66,21 +66,47 @@ Schemas for each parser can be found in the [`docs/parsers`](https://github.com/
Release notes can be found [here](https://blog.kellybrazil.com/category/jc-news/).
## Why Would Anyone Do This?!
For more information on the motivations for this project, please see my [blog post](https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/).
## Installation
There are several ways to get `jc`. You can install via `pip`, `brew`, DEB or RPM packages, or by downloading the correct binary for your architecture and running it anywhere on your filesystem.
There are several ways to get `jc`. You can install via `pip`; other OS package repositories like `dnf`, `zypper`, `nix-env`, `brew`, or `portsnap`; via DEB/RPM packages; or by downloading the correct binary for your architecture and running it anywhere on your filesystem.
### Pip (macOS, linux, unix, Windows)
```
$ pip3 install --upgrade jc
$ pip3 install jc
```
### Brew (macOS)
### OS Package Repositories
#### Dnf (Fedora linux)
```
# dnf install jc
```
or
```
# dnf --enablerepo=updates-testing install jc
```
#### Zypper (openSUSE linux)
```
# zypper install jc
```
#### Nix-env (NixOS linux)
```
$ nix-env -iA nixpkgs.jc
```
#### Brew (macOS)
```
$ brew install jc
```
#### Ports (FreeBSD)
```
# portsnap fetch update && cd /usr/ports/textproc/py-jc && make install clean
```
### Packages and Binaries
Please see https://kellyjonbrazil.github.io/jc-packaging/ for details.
@@ -105,6 +131,7 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio
- `--csv` enables the `CSV` file parser
- `--df` enables the `df` command parser
- `--dig` enables the `dig` command parser
- `--dmidecode` enables the `dmidecode` command parser
- `--du` enables the `du` command parser
- `--env` enables the `env` command parser
- `--file` enables the `file` command parser
@@ -149,7 +176,7 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio
### Options
- `-a` about `jc`. Prints information about `jc` and the parsers (in JSON, of course!)
- `-d` debug mode. Prints trace messages if parsing issues encountered
- `-d` debug mode. Prints trace messages if parsing issues encountered (use `-dd` for verbose debugging)
- `-m` monochrome JSON output
- `-p` pretty format the JSON output
- `-q` quiet mode. Suppresses warning messages
@@ -171,13 +198,22 @@ or
JC_COLORS=default,default,default,default
```
## Contributions
Feel free to add/improve code or parsers! You can use the [`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py) parser as a template and submit your parser with a pull request.
### Custom Parsers
Custom local 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 [`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py) parser as a template and simply place a `.py` file in the `jcparsers` subfolder.
Local plugin filenames must be valid python module names, therefore must consist entirely of alphanumerics and start with a letter. Local plugins may override default plugins.
> Note: The application data directory follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)
## Compatibility
Some parsers like `ls`, `ps`, `dig`, etc. will work on any platform. Other parsers that are platform-specific will generate a warning message if they are used 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 OSX laptop. In that case you can suppress the warning message with the `-q` cli option or the `quiet=True` function parameter in `parse()`:
```
@@ -187,16 +223,23 @@ $ cat lsof.out | jc --lsof -q
Tested on:
- Centos 7.7
- Ubuntu 18.4
- Fedora32
- OSX 10.11.6
- OSX 10.14.6
- NixOS
- FreeBSD12
## Contributions
Feel free to add/improve code or parsers! You can use the [`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py) parser as a template and submit your parser with a pull request.
## Acknowledgments
- CI automation and code optimizations from https://github.com/philippeitis
- `ifconfig-parser` module from https://github.com/KnightWhoSayNi/ifconfig-parser
- `xmltodict` module from https://github.com/martinblech/xmltodict by Martín Blech
- `ruamel.yaml` library from https://pypi.org/project/ruamel.yaml by Anthon van der Neut
- Parsing code from Conor Heine at https://gist.github.com/cahna/43a1a3ff4d075bcd71f9d7120037a501 adapted for some parsers
- Excellent constructive feedback from Ilya Sher (https://github.com/ilyash-b)
- Local parser plugin feature contributed by [Dean Serenevy](https://github.com/duelafn)
- CI automation and code optimizations by [philippeitis](https://github.com/philippeitis)
- [`ifconfig-parser`](https://github.com/KnightWhoSayNi/ifconfig-parser) module by KnightWhoSayNi
- [`xmltodict`](https://github.com/martinblech/xmltodict) module by Martín Blech
- [`ruamel.yaml`](https://pypi.org/project/ruamel.yaml) module by Anthon van der Neut
- Parsing [code](https://gist.github.com/cahna/43a1a3ff4d075bcd71f9d7120037a501) from Conor Heine adapted for some parsers
- Excellent constructive feedback from [Ilya Sher](https://github.com/ilyash-b)
## Examples
### airport -I
@@ -295,21 +338,25 @@ $ arp -a | jc --arp -p # or: jc -p arp -a
"address": "192.168.71.1",
"hwtype": "ether",
"hwaddress": "00:50:56:c0:00:08",
"iface": "ens33"
"iface": "ens33",
"permanent": true
},
{
"name": null,
"address": "192.168.71.254",
"hwtype": "ether",
"hwaddress": "00:50:56:fe:7a:b4",
"iface": "ens33"
"iface": "ens33",
"permanent": true
},
{
"name": "_gateway",
"address": "192.168.71.2",
"hwtype": "ether",
"hwaddress": "00:50:56:f7:4a:fc",
"iface": "ens33"
"iface": "ens33",
"permanent": false,
"expires": 110
}
]
```
@@ -340,7 +387,7 @@ $ blkid | jc --blkid -p # or: jc -p blkid
]
```
```
$ sudo blkid -o udev -ip /dev/sda2 | jc --blkid -p # or: sudo jc -p blkid -o udev -ip /dev/sda2
# blkid -o udev -ip /dev/sda2 | jc --blkid -p # or: jc -p blkid -o udev -ip /dev/sda2
[
{
"id_fs_uuid": "3klkIj-w1kk-DkJi-0XBJ-y3i7-i2Ac-vHqWBM",
@@ -735,6 +782,52 @@ $ dig -x 1.1.1.1 | jc --dig -p # or: jc -p dig -x 1.1.1.1
}
]
```
### dmidecode
```
# dmidecode | jc --dmidecode -p # or: jc -p dmidecode
[
{
"handle": "0x0000",
"type": 0,
"bytes": 24,
"description": "BIOS Information",
"values": {
"vendor": "Phoenix Technologies LTD",
"version": "6.00",
"release_date": "04/13/2018",
"address": "0xEA490",
"runtime_size": "88944 bytes",
"rom_size": "64 kB",
"characteristics": [
"ISA is supported",
"PCI is supported",
"PC Card (PCMCIA) is supported",
"PNP is supported",
"APM is supported",
"BIOS is upgradeable",
"BIOS shadowing is allowed",
"ESCD support is available",
"Boot from CD is supported",
"Selectable boot is supported",
"EDD is supported",
"Print screen service is supported (int 5h)",
"8042 keyboard services are supported (int 9h)",
"Serial services are supported (int 14h)",
"Printer services are supported (int 17h)",
"CGA/mono video services are supported (int 10h)",
"ACPI is supported",
"Smart battery is supported",
"BIOS boot specification is supported",
"Function key-initiated network boot is supported",
"Targeted content distribution is supported"
],
"bios_revision": "4.6",
"firmware_revision": "0.0"
}
},
...
]
```
### du
```
$ du /usr | jc --du -p # or: jc -p du /usr
@@ -1150,7 +1243,7 @@ $ cat example.ini | jc --ini -p
```
### iptables
```
$ sudo iptables --line-numbers -v -L -t nat | jc --iptables -p # or: sudo jc -p iptables --line-numbers -v -L -t nat
# iptables --line-numbers -v -L -t nat | jc --iptables -p # or: jc -p iptables --line-numbers -v -L -t nat
[
{
"chain": "PREROUTING",
@@ -1380,7 +1473,7 @@ $ lsmod | jc --lsmod -p # or: jc -p lsmod
```
### lsof
```
$ sudo lsof | jc --lsof -p # or: sudo jc -p lsof
# lsof | jc --lsof -p # or: jc -p lsof
[
{
"command": "systemd",
@@ -1467,7 +1560,7 @@ $ mount | jc --mount -p # or: jc -p mount
```
### netstat
```
$ sudo netstat -apee | jc --netstat -p # or: sudo jc -p netstat -apee
# netstat -apee | jc --netstat -p # or: jc -p netstat -apee
[
{
"proto": "tcp",
@@ -1616,6 +1709,85 @@ $ sudo netstat -apee | jc --netstat -p # or: sudo jc -p netstat -apee
},
...
]
$ netstat -r | jc --netstat -p # or: jc -p netstat -r
[
{
"destination": "default",
"gateway": "gateway",
"genmask": "0.0.0.0",
"route_flags": "UG",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "ens33",
"kind": "route",
"route_flags_pretty": [
"UP",
"GATEWAY"
]
},
{
"destination": "172.17.0.0",
"gateway": "0.0.0.0",
"genmask": "255.255.0.0",
"route_flags": "U",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "docker0",
"kind": "route",
"route_flags_pretty": [
"UP"
]
},
{
"destination": "192.168.71.0",
"gateway": "0.0.0.0",
"genmask": "255.255.255.0",
"route_flags": "U",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "ens33",
"kind": "route",
"route_flags_pretty": [
"UP"
]
}
]
$ netstat -i | jc --netstat -p # or: jc -p netstat -i
[
{
"iface": "ens33",
"mtu": 1500,
"rx_ok": 476,
"rx_err": 0,
"rx_drp": 0,
"rx_ovr": 0,
"tx_ok": 312,
"tx_err": 0,
"tx_drp": 0,
"tx_ovr": 0,
"flg": "BMRU",
"kind": "interface"
},
{
"iface": "lo",
"mtu": 65536,
"rx_ok": 0,
"rx_err": 0,
"rx_drp": 0,
"rx_ovr": 0,
"tx_ok": 0,
"tx_err": 0,
"tx_drp": 0,
"tx_ovr": 0,
"flg": "LRU",
"kind": "interface"
}
]
```
### ntpq
```
@@ -1821,48 +1993,42 @@ $ route -ee | jc --route -p # or: jc -p route -ee
[
{
"destination": "default",
"gateway": "gateway",
"gateway": "_gateway",
"genmask": "0.0.0.0",
"flags": "UG",
"metric": 100,
"metric": 202,
"ref": 0,
"use": 0,
"iface": "ens33",
"mss": 0,
"window": 0,
"irtt": 0
},
{
"destination": "172.17.0.0",
"gateway": "0.0.0.0",
"genmask": "255.255.0.0",
"flags": "U",
"metric": 0,
"ref": 0,
"use": 0,
"iface": "docker",
"mss": 0,
"window": 0,
"irtt": 0
"irtt": 0,
"flags_pretty": [
"UP",
"GATEWAY"
]
},
{
"destination": "192.168.71.0",
"gateway": "0.0.0.0",
"genmask": "255.255.255.0",
"flags": "U",
"metric": 100,
"metric": 202,
"ref": 0,
"use": 0,
"iface": "ens33",
"mss": 0,
"window": 0,
"irtt": 0
"irtt": 0,
"flags_pretty": [
"UP"
]
}
]
```
### /etc/shadow file
```
$ sudo cat /etc/shadow | jc --shadow -p
# cat /etc/shadow | jc --shadow -p
[
{
"username": "root",
@@ -1899,7 +2065,7 @@ $ sudo cat /etc/shadow | jc --shadow -p
```
### ss
```
$ sudo ss -a | jc --ss -p # or: sudo jc -p ss -a
# ss -a | jc --ss -p # or: jc -p ss -a
[
{
"netid": "nl",

View File

@@ -1,5 +1,71 @@
jc changelog
20200625 v1.11.8
- Add verbose debug option using -dd argument
20200622 v1.11.7
- Fix iptables parser issue which would not output the last chain
20200614 v1.11.6
- Improve and standardize empty data check for all parsers
20200612 v1.11.5
- Update airport_s parser to fix error on parsing empty data
- Update arp parser to fix error on parsing empty data
- Update blkid parser to fix error on parsing empty data
- Update crontab parser to fix error on parsing empty data
- Update crontab_u parser to fix error on parsing empty data
- Update df parser to fix error on parsing empty data
- Update free parser to fix error on parsing empty data
- Update lsblk parser to fix error on parsing empty data
- Update lsmod parser to fix error on parsing empty data
- Update mount parser to fix error on parsing empty data
- Update netstat parser to fix error on parsing empty data
- Update ntpq parser to fix error on parsing empty data
- Update ps parser to fix error on parsing empty data
- Update route parser to fix error on parsing empty data
- Update systemctl parser to fix error on parsing empty data
- Update systemctl_lj parser to fix error on parsing empty data
- Update systemctl_ls parser to fix error on parsing empty data
- Update systemctl_luf parser to fix error on parsing empty data
- Update uptime parser to fix error on parsing empty data
- Update w parser to fix error on parsing empty data
- Update xml parser to fix error on parsing empty data
- Add tests to all parsers for no data condition
- Update ss parser to fix integer fields
20200610 v1.11.4
- Update ls parser to fix error on parsing an empty directory
20200609 v1.11.3
- Add local parser plugin feature (contributed by Dean Serenevy)
20200530 v1.11.2
- Update netstat parser to add freebsd support
- Update netstat parser to add route_flags_pretty field
- Update netstat parser to change osx_inode field name to unix_inode
- Update netstat parser to change osx_flags field name to unix_flags
- Update netstat parser to strip whitespace from state field
- Update route parser to add flags_pretty field
- Update arp parser to add permanent field (freebsd and osx)
- Update arp parser to add expires field (freebsd)
- Update w parser to strip whitespace from what field
- Update last parser to fix FreeBSD issues
- Update stat parser to change osx_flags field name to unix_flags
- Update stat parser to add unix_device field for freebsd and osx
- Fix freebsd compatibility message for df, fstab, mount, ntpq, stat, and uname parsers
- Fix compatibility message for platforms that include the version number at the end (e.g. freebsd12)
20200523 v1.11.1
- Update stat command parser to change osx_flags field to string
20200522 v1.11.0
- Add dmidecode command parser
- Update stat command parser to add OSX support
- Update netstat command parser to add OSX support
- Update netstat command parser to add -r (route) functionality for linux and OSX
- Update netstat command parser to add -i (interface) functionality for linux and OSX
20200511 v1.10.12
- Remove shebang from jc/cli.py for Fedora packaging

View File

@@ -13,6 +13,7 @@ pydocmd simple jc.parsers.crontab_u+ > ../docs/parsers/crontab_u.md
pydocmd simple jc.parsers.csv+ > ../docs/parsers/csv.md
pydocmd simple jc.parsers.df+ > ../docs/parsers/df.md
pydocmd simple jc.parsers.dig+ > ../docs/parsers/dig.md
pydocmd simple jc.parsers.dmidecode+ > ../docs/parsers/dmidecode.md
pydocmd simple jc.parsers.du+ > ../docs/parsers/du.md
pydocmd simple jc.parsers.env+ > ../docs/parsers/env.md
pydocmd simple jc.parsers.file+ > ../docs/parsers/file.md

View File

@@ -59,6 +59,8 @@ Examples:
"hwtype": "ether",
"hwaddress": "00:50:56:f0:98:26",
"iface": "ens33"
"permanent": false,
"expires": 1182
},
{
"name": "gateway",
@@ -66,6 +68,8 @@ Examples:
"hwtype": "ether",
"hwaddress": "00:50:56:f7:4a:fc",
"iface": "ens33"
"permanent": false,
"expires": 110
}
]
@@ -77,6 +81,8 @@ Examples:
"hwtype": "ether",
"hwaddress": "00:50:56:fe:7a:b4",
"iface": "ens33"
"permanent": false,
"expires": "1182"
},
{
"name": "_gateway",
@@ -84,6 +90,8 @@ Examples:
"hwtype": "ether",
"hwaddress": "00:50:56:f7:4a:fc",
"iface": "ens33"
"permanent": false,
"expires": "110"
}
]
@@ -114,7 +122,9 @@ Returns:
"hwtype": string,
"hwaddress": string,
"flags_mask": string,
"iface": string
"iface": string,
"permanent": boolean,
"expires": integer
}
]

View File

@@ -7,7 +7,7 @@ Usage:
Compatibility:
'linux', 'darwin'
'linux', 'darwin', 'freebsd'
Examples:

153
docs/parsers/dmidecode.md Normal file
View File

@@ -0,0 +1,153 @@
# jc.parsers.dmidecode
jc - JSON CLI output utility dmidecode Parser
Usage:
specify --dmidecode as the first argument if the piped input is coming from dmidecode
Compatibility:
'linux'
Examples:
# dmidecode | jc --dmidecode -p
[
{
"handle": "0x0000",
"type": 0,
"bytes": 24,
"description": "BIOS Information",
"values": {
"vendor": "Phoenix Technologies LTD",
"version": "6.00",
"release_date": "04/13/2018",
"address": "0xEA490",
"runtime_size": "88944 bytes",
"rom_size": "64 kB",
"characteristics": [
"ISA is supported",
"PCI is supported",
"PC Card (PCMCIA) is supported",
"PNP is supported",
"APM is supported",
"BIOS is upgradeable",
"BIOS shadowing is allowed",
"ESCD support is available",
"Boot from CD is supported",
"Selectable boot is supported",
"EDD is supported",
"Print screen service is supported (int 5h)",
"8042 keyboard services are supported (int 9h)",
"Serial services are supported (int 14h)",
"Printer services are supported (int 17h)",
"CGA/mono video services are supported (int 10h)",
"ACPI is supported",
"Smart battery is supported",
"BIOS boot specification is supported",
"Function key-initiated network boot is supported",
"Targeted content distribution is supported"
],
"bios_revision": "4.6",
"firmware_revision": "0.0"
}
},
...
]
# dmidecode | jc --dmidecode -p -r
[
{
"handle": "0x0000",
"type": "0",
"bytes": "24",
"description": "BIOS Information",
"values": {
"vendor": "Phoenix Technologies LTD",
"version": "6.00",
"release_date": "04/13/2018",
"address": "0xEA490",
"runtime_size": "88944 bytes",
"rom_size": "64 kB",
"characteristics": [
"ISA is supported",
"PCI is supported",
"PC Card (PCMCIA) is supported",
"PNP is supported",
"APM is supported",
"BIOS is upgradeable",
"BIOS shadowing is allowed",
"ESCD support is available",
"Boot from CD is supported",
"Selectable boot is supported",
"EDD is supported",
"Print screen service is supported (int 5h)",
"8042 keyboard services are supported (int 9h)",
"Serial services are supported (int 14h)",
"Printer services are supported (int 17h)",
"CGA/mono video services are supported (int 10h)",
"ACPI is supported",
"Smart battery is supported",
"BIOS boot specification is supported",
"Function key-initiated network boot is supported",
"Targeted content distribution is supported"
],
"bios_revision": "4.6",
"firmware_revision": "0.0"
}
},
...
]
## info
```python
info(self, /, *args, **kwargs)
```
## process
```python
process(proc_data)
```
Final processing to conform to the schema.
Parameters:
proc_data: (dictionary) raw structured data to process
Returns:
List of dictionaries. Structured data with the following schema:
[
{
"handle": string,
"type": integer,
"bytes": integer,
"description": string,
"values": { (null if empty)
"lowercase_no_spaces_keys": string,
"multiline_key_values": [
string,
]
}
}
]
## parse
```python
parse(data, raw=False, quiet=False)
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) output preprocessed JSON if True
quiet: (boolean) suppress warning messages if True
Returns:
List of dictionaries. Raw or processed structured data.

View File

@@ -7,7 +7,7 @@ Usage:
Compatibility:
'linux'
'linux', 'freebsd'
Examples:

View File

@@ -7,7 +7,7 @@ Usage:
Compatibility:
'linux', 'darwin'
'linux', 'darwin', 'freebsd'
Example:

View File

@@ -5,13 +5,18 @@ Usage:
Specify --netstat as the first argument if the piped input is coming from netstat
Caveats:
- Use of multiple 'l' options is not supported on OSX (e.g. 'netstat -rlll')
- Use of the 'A' option is not supported on OSX when using the 'r' option (e.g. netstat -rA)
Compatibility:
'linux'
'linux', 'darwin', 'freebsd'
Examples:
$ sudo netstat -apee | jc --netstat -p
# netstat -apee | jc --netstat -p
[
{
"proto": "tcp",
@@ -161,152 +166,83 @@ Examples:
...
]
$ sudo netstat -apee | jc --netstat -p -r
$ netstat -r | jc --netstat -p
[
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "0.0.0.0",
"state": "LISTEN",
"user": "systemd-resolve",
"inode": "26958",
"program_name": "systemd-resolve",
"kind": "network",
"pid": "887",
"local_port": "domain",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
"destination": "default",
"gateway": "gateway",
"genmask": "0.0.0.0",
"route_flags": "UG",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "ens33",
"kind": "route",
"route_flags_pretty": [
"UP",
"GATEWAY"
]
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "0.0.0.0",
"foreign_address": "0.0.0.0",
"state": "LISTEN",
"user": "root",
"inode": "30499",
"program_name": "sshd",
"kind": "network",
"pid": "1186",
"local_port": "ssh",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
"destination": "172.17.0.0",
"gateway": "0.0.0.0",
"genmask": "255.255.0.0",
"route_flags": "U",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "docker0",
"kind": "route",
"route_flags_pretty": [
"UP"
]
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "localhost",
"state": "ESTABLISHED",
"user": "root",
"inode": "46829",
"program_name": "sshd: root",
"kind": "network",
"pid": "2242",
"local_port": "ssh",
"foreign_port": "52186",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
"destination": "192.168.71.0",
"gateway": "0.0.0.0",
"genmask": "255.255.255.0",
"route_flags": "U",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "ens33",
"kind": "route",
"route_flags_pretty": [
"UP"
]
}
]
$ netstat -i | jc --netstat -p
[
{
"iface": "ens33",
"mtu": 1500,
"rx_ok": 476,
"rx_err": 0,
"rx_drp": 0,
"rx_ovr": 0,
"tx_ok": 312,
"tx_err": 0,
"tx_drp": 0,
"tx_ovr": 0,
"flg": "BMRU",
"kind": "interface"
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "localhost",
"state": "ESTABLISHED",
"user": "root",
"inode": "46828",
"program_name": "ssh",
"kind": "network",
"pid": "2241",
"local_port": "52186",
"foreign_port": "ssh",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
},
{
"proto": "tcp6",
"recv_q": "0",
"send_q": "0",
"local_address": "[::]",
"foreign_address": "[::]",
"state": "LISTEN",
"user": "root",
"inode": "30510",
"program_name": "sshd",
"kind": "network",
"pid": "1186",
"local_port": "ssh",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv6"
},
{
"proto": "udp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "0.0.0.0",
"state": null,
"user": "systemd-resolve",
"inode": "26957",
"program_name": "systemd-resolve",
"kind": "network",
"pid": "887",
"local_port": "domain",
"foreign_port": "*",
"transport_protocol": "udp",
"network_protocol": "ipv4"
},
{
"proto": "raw6",
"recv_q": "0",
"send_q": "0",
"local_address": "[::]",
"foreign_address": "[::]",
"state": "7",
"user": "systemd-network",
"inode": "27001",
"program_name": "systemd-network",
"kind": "network",
"pid": "867",
"local_port": "ipv6-icmp",
"foreign_port": "*",
"transport_protocol": null,
"network_protocol": "ipv6"
},
{
"proto": "unix",
"refcnt": "2",
"flags": null,
"type": "DGRAM",
"state": null,
"inode": "33322",
"program_name": "systemd",
"path": "/run/user/1000/systemd/notify",
"kind": "socket",
"pid": " 1607"
},
{
"proto": "unix",
"refcnt": "2",
"flags": "ACC",
"type": "SEQPACKET",
"state": "LISTENING",
"inode": "20835",
"program_name": "init",
"path": "/run/udev/control",
"kind": "socket",
"pid": " 1"
},
...
"iface": "lo",
"mtu": 65536,
"rx_ok": 0,
"rx_err": 0,
"rx_drp": 0,
"rx_ovr": 0,
"tx_ok": 0,
"tx_err": 0,
"tx_drp": 0,
"tx_ovr": 0,
"flg": "LRU",
"kind": "interface"
}
]
## info
@@ -331,28 +267,100 @@ Returns:
[
{
"proto": string,
"recv_q": integer,
"send_q": integer,
"transport_protocol" string,
"network_protocol": string,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"foreign_address": string,
"foreign_port": string,
"foreign_port_num": integer,
"state": string,
"program_name": string,
"pid": integer,
"user": string,
"security_context": string,
"refcnt": integer,
"flags": string,
"type": string,
"inode": integer,
"path": string,
"kind": string
"proto": string,
"recv_q": integer,
"send_q": integer,
"transport_protocol" string,
"network_protocol": string,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"foreign_address": string,
"foreign_port": string,
"foreign_port_num": integer,
"state": string,
"program_name": string,
"pid": integer,
"user": string,
"security_context": string,
"refcnt": integer,
"flags": string,
"type": string,
"inode": integer,
"path": string,
"kind": string,
"address": string,
"unix_inode": string,
"conn": string,
"refs": string,
"nextref": string,
"name": string,
"unit": integer,
"vendor": integer,
"class": integer,
"subcla": integer,
"unix_flags": integer,
"pcbcount": integer,
"rcvbuf": integer,
"sndbuf": integer,
"rxbytes": integer,
"txbytes": integer,
"destination": string,
"gateway": string,
"route_flags": string,
"route_flags_pretty": [
string,
]
"route_refs": integer,
"use": integer,
"mtu": integer,
"expire": string,
"genmask": string,
"mss": integer,
"window": integer,
"irtt": integer,
"iface": string,
"metric": integer,
"network": string,
"address": string,
"ipkts": integer, - = null
"ierrs": integer, - = null
"idrop": integer, - = null
"opkts": integer, - = null
"oerrs": integer, - = null
"coll": integer, - = null
"rx_ok": integer,
"rx_err": integer,
"rx_drp": integer,
"rx_ovr": integer,
"tx_ok": integer,
"tx_err": integer,
"tx_drp": integer,
"tx_ovr": integer,
"flg": string,
"ibytes": integer,
"obytes": integer,
"r_mbuf": integer,
"s_mbuf": integer,
"r_clus": integer,
"s_clus": integer,
"r_hiwa": integer,
"s_hiwa": integer,
"r_lowa": integer,
"s_lowa": integer,
"r_bcnt": integer,
"s_bcnt": integer,
"r_bmax": integer,
"s_bmax": integer,
"rexmit": integer,
"ooorcv": integer,
"0_win": integer,
"rexmt": float,
"persist": float,
"keep": float,
"2msl": float,
"delack": float,
"rcvtime": float,
}
]

View File

@@ -7,7 +7,7 @@ Usage:
Compatibility:
'linux'
'linux', 'freebsd'
Examples:

View File

@@ -15,53 +15,48 @@ Examples:
[
{
"destination": "default",
"gateway": "gateway",
"gateway": "_gateway",
"genmask": "0.0.0.0",
"flags": "UG",
"metric": 100,
"metric": 202,
"ref": 0,
"use": 0,
"iface": "ens33",
"mss": 0,
"window": 0,
"irtt": 0
},
{
"destination": "172.17.0.0",
"gateway": "0.0.0.0",
"genmask": "255.255.0.0",
"flags": "U",
"metric": 0,
"ref": 0,
"use": 0,
"iface": "docker",
"mss": 0,
"window": 0,
"irtt": 0
"irtt": 0,
"flags_pretty": [
"UP",
"GATEWAY"
]
},
{
"destination": "192.168.71.0",
"gateway": "0.0.0.0",
"genmask": "255.255.255.0",
"flags": "U",
"metric": 100,
"metric": 202,
"ref": 0,
"use": 0,
"iface": "ens33",
"mss": 0,
"window": 0,
"irtt": 0
"irtt": 0,
"flags_pretty": [
"UP"
]
}
]
$ route -ee | jc --route -p -r
[
{
"destination": "default",
"gateway": "gateway",
"gateway": "_gateway",
"genmask": "0.0.0.0",
"flags": "UG",
"metric": "100",
"metric": "202",
"ref": "0",
"use": "0",
"iface": "ens33",
@@ -69,25 +64,12 @@ Examples:
"window": "0",
"irtt": "0"
},
{
"destination": "172.17.0.0",
"gateway": "0.0.0.0",
"genmask": "255.255.0.0",
"flags": "U",
"metric": "0",
"ref": "0",
"use": "0",
"iface": "docker",
"mss": "0",
"window": "0",
"irtt": "0"
},
{
"destination": "192.168.71.0",
"gateway": "0.0.0.0",
"genmask": "255.255.255.0",
"flags": "U",
"metric": "100",
"metric": "202",
"ref": "0",
"use": "0",
"iface": "ens33",
@@ -97,6 +79,7 @@ Examples:
}
]
## info
```python
info(self, /, *args, **kwargs)
@@ -119,17 +102,20 @@ Returns:
[
{
"destination": string,
"gateway": string,
"genmask": string,
"flags": string,
"metric": integer,
"ref": integer,
"use": integer,
"mss": integer,
"window": integer,
"irtt": integer,
"iface": string
"destination": string,
"gateway": string,
"genmask": string,
"flags": string,
"flags_pretty": [
string,
]
"metric": integer,
"ref": integer,
"use": integer,
"mss": integer,
"window": integer,
"irtt": integer,
"iface": string
}
]

View File

@@ -7,7 +7,7 @@ Usage:
Compatibility:
'linux'
'linux', 'darwin', 'freebsd'
Examples:
@@ -141,7 +141,11 @@ Returns:
"access_time": string, # - = null
"modify_time": string, # - = null
"change_time": string, # - = null
"birth_time": string # - = null
"birth_time": string, # - = null
"unix_device": integer,
"rdev": integer,
"block_size": integer,
"unix_flags": string
}
]

View File

@@ -11,7 +11,7 @@ Limitations:
Compatibility:
'linux', 'darwin'
'linux', 'darwin', 'freebsd'
Example:

View File

@@ -48,3 +48,18 @@ Returns:
no return, just prints output to STDERR
## has_data
```python
has_data(data)
```
Checks if the input contains data. If there are any non-whitespace characters then return True, else return False
Parameters:
data: (string) input to check whether it contains data
Returns:
Boolean True if input string (data) contains non-whitespace characters, otherwise False

611
jc/appdirs.py Normal file
View File

@@ -0,0 +1,611 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2005-2010 ActiveState Software Inc.
# Copyright (c) 2013 Eddy Petrișor
'''
# This is the MIT license
Copyright (c) 2010 ActiveState Software Inc.
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.
'''
"""Utilities for determining application-specific dirs.
See <https://github.com/ActiveState/appdirs> for details and usage.
"""
# Dev Notes:
# - MSDN on where to store app data files:
# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
# - XDG spec for Un*x: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
__version__ = "1.4.4"
__version_info__ = tuple(int(segment) for segment in __version__.split("."))
import sys
import os
PY3 = sys.version_info[0] == 3
if PY3:
unicode = str
if sys.platform.startswith('java'):
import platform
os_name = platform.java_ver()[3][0]
if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc.
system = 'win32'
elif os_name.startswith('Mac'): # "Mac OS X", etc.
system = 'darwin'
else: # "Linux", "SunOS", "FreeBSD", etc.
# Setting this to "linux2" is not ideal, but only Windows or Mac
# are actually checked for and the rest of the module expects
# *sys.platform* style strings.
system = 'linux2'
else:
system = sys.platform
def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"roaming" (boolean, default False) can be set True to use the Windows
roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be
sync'd on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
for a discussion of issues.
Typical user data directories are:
Mac OS X: ~/Library/Application Support/<AppName>
Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
That means, by default "~/.local/share/<AppName>".
"""
if system == "win32":
if appauthor is None:
appauthor = appname
const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
path = os.path.normpath(_get_win_folder(const))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
elif system == 'darwin':
path = os.path.expanduser('~/Library/Application Support/')
if appname:
path = os.path.join(path, appname)
else:
path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
r"""Return full path to the user-shared data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"multipath" is an optional parameter only applicable to *nix
which indicates that the entire list of data dirs should be
returned. By default, the first item from XDG_DATA_DIRS is
returned, or '/usr/local/share/<AppName>',
if XDG_DATA_DIRS is not set
Typical site data directories are:
Mac OS X: /Library/Application Support/<AppName>
Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
For Unix, this is using the $XDG_DATA_DIRS[0] default.
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
"""
if system == "win32":
if appauthor is None:
appauthor = appname
path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
elif system == 'darwin':
path = os.path.expanduser('/Library/Application Support')
if appname:
path = os.path.join(path, appname)
else:
# XDG default for $XDG_DATA_DIRS
# only first, if multipath is False
path = os.getenv('XDG_DATA_DIRS',
os.pathsep.join(['/usr/local/share', '/usr/share']))
pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
if appname:
if version:
appname = os.path.join(appname, version)
pathlist = [os.sep.join([x, appname]) for x in pathlist]
if multipath:
path = os.pathsep.join(pathlist)
else:
path = pathlist[0]
return path
if appname and version:
path = os.path.join(path, version)
return path
def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific config dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"roaming" (boolean, default False) can be set True to use the Windows
roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be
sync'd on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
for a discussion of issues.
Typical user config directories are:
Mac OS X: ~/Library/Preferences/<AppName>
Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
Win *: same as user_data_dir
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
That means, by default "~/.config/<AppName>".
"""
if system == "win32":
path = user_data_dir(appname, appauthor, None, roaming)
elif system == 'darwin':
path = os.path.expanduser('~/Library/Preferences/')
if appname:
path = os.path.join(path, appname)
else:
path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
r"""Return full path to the user-shared data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"multipath" is an optional parameter only applicable to *nix
which indicates that the entire list of config dirs should be
returned. By default, the first item from XDG_CONFIG_DIRS is
returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
Typical site config directories are:
Mac OS X: same as site_data_dir
Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
$XDG_CONFIG_DIRS
Win *: same as site_data_dir
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
"""
if system == 'win32':
path = site_data_dir(appname, appauthor)
if appname and version:
path = os.path.join(path, version)
elif system == 'darwin':
path = os.path.expanduser('/Library/Preferences')
if appname:
path = os.path.join(path, appname)
else:
# XDG default for $XDG_CONFIG_DIRS
# only first, if multipath is False
path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
if appname:
if version:
appname = os.path.join(appname, version)
pathlist = [os.sep.join([x, appname]) for x in pathlist]
if multipath:
path = os.pathsep.join(pathlist)
else:
path = pathlist[0]
return path
def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
r"""Return full path to the user-specific cache dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"opinion" (boolean) can be False to disable the appending of
"Cache" to the base app data dir for Windows. See
discussion below.
Typical user cache directories are:
Mac OS X: ~/Library/Caches/<AppName>
Unix: ~/.cache/<AppName> (XDG default)
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
On Windows the only suggestion in the MSDN docs is that local settings go in
the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
app data dir (the default returned by `user_data_dir` above). Apps typically
put cache data somewhere *under* the given dir here. Some examples:
...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
...\Acme\SuperApp\Cache\1.0
OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
This can be disabled with the `opinion=False` option.
"""
if system == "win32":
if appauthor is None:
appauthor = appname
path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
if opinion:
path = os.path.join(path, "Cache")
elif system == 'darwin':
path = os.path.expanduser('~/Library/Caches')
if appname:
path = os.path.join(path, appname)
else:
path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def user_state_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific state dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"roaming" (boolean, default False) can be set True to use the Windows
roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be
sync'd on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
for a discussion of issues.
Typical user state directories are:
Mac OS X: same as user_data_dir
Unix: ~/.local/state/<AppName> # or in $XDG_STATE_HOME, if defined
Win *: same as user_data_dir
For Unix, we follow this Debian proposal <https://wiki.debian.org/XDGBaseDirectorySpecification#state>
to extend the XDG spec and support $XDG_STATE_HOME.
That means, by default "~/.local/state/<AppName>".
"""
if system in ["win32", "darwin"]:
path = user_data_dir(appname, appauthor, None, roaming)
else:
path = os.getenv('XDG_STATE_HOME', os.path.expanduser("~/.local/state"))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
r"""Return full path to the user-specific log dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"opinion" (boolean) can be False to disable the appending of
"Logs" to the base app data dir for Windows, and "log" to the
base cache dir for Unix. See discussion below.
Typical user log directories are:
Mac OS X: ~/Library/Logs/<AppName>
Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
On Windows the only suggestion in the MSDN docs is that local settings
go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
examples of what some windows apps use for a logs dir.)
OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
value for Windows and appends "log" to the user cache dir for Unix.
This can be disabled with the `opinion=False` option.
"""
if system == "darwin":
path = os.path.join(
os.path.expanduser('~/Library/Logs'),
appname)
elif system == "win32":
path = user_data_dir(appname, appauthor, version)
version = False
if opinion:
path = os.path.join(path, "Logs")
else:
path = user_cache_dir(appname, appauthor, version)
version = False
if opinion:
path = os.path.join(path, "log")
if appname and version:
path = os.path.join(path, version)
return path
class AppDirs(object):
"""Convenience wrapper for getting application dirs."""
def __init__(self, appname=None, appauthor=None, version=None,
roaming=False, multipath=False):
self.appname = appname
self.appauthor = appauthor
self.version = version
self.roaming = roaming
self.multipath = multipath
@property
def user_data_dir(self):
return user_data_dir(self.appname, self.appauthor,
version=self.version, roaming=self.roaming)
@property
def site_data_dir(self):
return site_data_dir(self.appname, self.appauthor,
version=self.version, multipath=self.multipath)
@property
def user_config_dir(self):
return user_config_dir(self.appname, self.appauthor,
version=self.version, roaming=self.roaming)
@property
def site_config_dir(self):
return site_config_dir(self.appname, self.appauthor,
version=self.version, multipath=self.multipath)
@property
def user_cache_dir(self):
return user_cache_dir(self.appname, self.appauthor,
version=self.version)
@property
def user_state_dir(self):
return user_state_dir(self.appname, self.appauthor,
version=self.version)
@property
def user_log_dir(self):
return user_log_dir(self.appname, self.appauthor,
version=self.version)
#---- internal support stuff
def _get_win_folder_from_registry(csidl_name):
"""This is a fallback technique at best. I'm not sure if using the
registry for this guarantees us the correct answer for all CSIDL_*
names.
"""
if PY3:
import winreg as _winreg
else:
import _winreg
shell_folder_name = {
"CSIDL_APPDATA": "AppData",
"CSIDL_COMMON_APPDATA": "Common AppData",
"CSIDL_LOCAL_APPDATA": "Local AppData",
}[csidl_name]
key = _winreg.OpenKey(
_winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
)
dir, type = _winreg.QueryValueEx(key, shell_folder_name)
return dir
def _get_win_folder_with_ctypes(csidl_name):
import ctypes
csidl_const = {
"CSIDL_APPDATA": 26,
"CSIDL_COMMON_APPDATA": 35,
"CSIDL_LOCAL_APPDATA": 28,
}[csidl_name]
buf = ctypes.create_unicode_buffer(1024)
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in buf:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
buf2 = ctypes.create_unicode_buffer(1024)
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
buf = buf2
return buf.value
def _get_win_folder_with_jna(csidl_name):
import array
from com.sun import jna
from com.sun.jna.platform import win32
buf_size = win32.WinDef.MAX_PATH * 2
buf = array.zeros('c', buf_size)
shell = win32.Shell32.INSTANCE
shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf)
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in dir:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
buf = array.zeros('c', buf_size)
kernel = win32.Kernel32.INSTANCE
if kernel.GetShortPathName(dir, buf, buf_size):
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
return dir
if system == "win32":
try:
from ctypes import windll
except ImportError:
try:
import com.sun.jna
except ImportError:
_get_win_folder = _get_win_folder_from_registry
else:
_get_win_folder = _get_win_folder_with_jna
else:
_get_win_folder = _get_win_folder_with_ctypes
#---- self test code
if __name__ == "__main__":
appname = "MyApp"
appauthor = "MyCompany"
props = ("user_data_dir",
"user_config_dir",
"user_cache_dir",
"user_state_dir",
"user_log_dir",
"site_data_dir",
"site_config_dir")
print("-- app dirs %s --" % __version__)
print("-- app dirs (with optional 'version')")
dirs = AppDirs(appname, appauthor, version="1.0")
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (without optional 'version')")
dirs = AppDirs(appname, appauthor)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (without optional 'appauthor')")
dirs = AppDirs(appname)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (with disabled 'appauthor')")
dirs = AppDirs(appname, appauthor=False)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))

View File

@@ -1,8 +1,11 @@
"""jc - JSON CLI output utility
JC cli module
"""
import sys
import os
import os.path
import re
import shlex
import importlib
import textwrap
@@ -14,10 +17,11 @@ from pygments.token import (Name, Number, String, Keyword)
from pygments.lexers import JsonLexer
from pygments.formatters import Terminal256Formatter
import jc.utils
import jc.appdirs as appdirs
class info():
version = '1.10.12'
version = '1.11.8'
description = 'jc cli output JSON conversion tool'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -35,6 +39,7 @@ parsers = [
'csv',
'df',
'dig',
'dmidecode',
'du',
'env',
'file',
@@ -78,6 +83,20 @@ parsers = [
'yaml'
]
# List of custom or override parsers.
# Allow any <user_data_dir>/jc/jcparsers/*.py
local_parsers = []
data_dir = appdirs.user_data_dir("jc", "jc")
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)):
plugin_name = name[0:-3]
local_parsers.append(plugin_name)
if plugin_name not in parsers:
parsers.append(plugin_name)
def set_env_colors():
"""
@@ -121,10 +140,10 @@ def set_env_colors():
# Try the color set in the JC_COLORS env variable first. If it is set to default, then fall back to default colors
return {
Name.Tag: f'bold ansi{color_list[0]}' if not color_list[0] == 'default' else f'bold ansiblue', # key names
Keyword: f'ansi{color_list[1]}' if not color_list[1] == 'default' else f'ansibrightblack', # true, false, null
Number: f'ansi{color_list[2]}' if not color_list[2] == 'default' else f'ansimagenta', # numbers
String: f'ansi{color_list[3]}' if not color_list[3] == 'default' else f'ansigreen' # strings
Name.Tag: f'bold ansi{color_list[0]}' if not color_list[0] == 'default' else 'bold ansiblue', # key names
Keyword: f'ansi{color_list[1]}' if not color_list[1] == 'default' else 'ansibrightblack', # true, false, null
Number: f'ansi{color_list[2]}' if not color_list[2] == 'default' else 'ansimagenta', # numbers
String: f'ansi{color_list[3]}' if not color_list[3] == 'default' else 'ansigreen' # strings
}
@@ -158,8 +177,9 @@ def parser_mod_shortname(parser):
def parser_module(parser):
"""import the module just in time and return the module object"""
importlib.import_module('jc.parsers.' + parser_mod_shortname(parser))
return getattr(jc.parsers, parser_mod_shortname(parser))
shortname = parser_mod_shortname(parser)
path = ('jcparsers.' if shortname in local_parsers else 'jc.parsers.')
return importlib.import_module(path + shortname)
def parsers_text(indent=0, pad=0):
@@ -227,7 +247,7 @@ def helptext(message):
{parsers_string}
Options:
-a about jc
-d debug - show trace messages
-d debug - show traceback (-dd for verbose traceback)
-m monochrome output
-p pretty print output
-q quiet - suppress warnings
@@ -244,12 +264,12 @@ def helptext(message):
def json_out(data, pretty=False, mono=False, piped_out=False):
# set colors
class JcStyle(Style):
styles = set_env_colors()
if not mono and not piped_out:
# set colors
class JcStyle(Style):
styles = set_env_colors()
if pretty:
print(highlight(json.dumps(data, indent=2), JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
else:
@@ -350,6 +370,7 @@ def main():
options.extend(opt[1:])
debug = 'd' in options
verbose_debug = True if options.count('d') > 1 else False
mono = 'm' in options
pretty = 'p' in options
quiet = 'q' in options
@@ -368,6 +389,10 @@ def main():
found = False
if debug:
if verbose_debug:
import cgitb
cgitb.enable(display=1, logdir=None, context=5, format="text")
for arg in sys.argv:
parser_name = parser_shortname(arg)

View File

@@ -55,7 +55,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'airport -I command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -131,9 +131,11 @@ def parse(data, raw=False, quiet=False):
raw_output = {}
for line in filter(None, data.splitlines()):
linedata = line.split(':', maxsplit=1)
raw_output[linedata[0].strip().lower().replace(' ', '_').replace('.', '_')] = linedata[1].strip()
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(' ', '_').replace('.', '_')] = linedata[1].strip()
if raw:
return raw_output

View File

@@ -88,7 +88,7 @@ import jc.parsers.universal
class info():
version = '1.0'
version = '1.2'
description = 'airport -s command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -170,15 +170,17 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()
raw_output = []
cleandata = list(filter(None, data.splitlines()))
# fix headers
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace('-', '_')
cleandata[0] = cleandata[0].replace('security (auth/unicast/group)', 'security')
if jc.utils.has_data(data):
# fix headers
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace('-', '_')
cleandata[0] = cleandata[0].replace('security (auth/unicast/group)', 'security')
# parse the data
raw_output = jc.parsers.universal.sparse_table_parse(cleandata)
# parse the data
raw_output = jc.parsers.universal.sparse_table_parse(cleandata)
if raw:
return raw_output

View File

@@ -58,6 +58,8 @@ Examples:
"hwtype": "ether",
"hwaddress": "00:50:56:f0:98:26",
"iface": "ens33"
"permanent": false,
"expires": 1182
},
{
"name": "gateway",
@@ -65,6 +67,8 @@ Examples:
"hwtype": "ether",
"hwaddress": "00:50:56:f7:4a:fc",
"iface": "ens33"
"permanent": false,
"expires": 110
}
]
@@ -76,6 +80,8 @@ Examples:
"hwtype": "ether",
"hwaddress": "00:50:56:fe:7a:b4",
"iface": "ens33"
"permanent": false,
"expires": "1182"
},
{
"name": "_gateway",
@@ -83,6 +89,8 @@ Examples:
"hwtype": "ether",
"hwaddress": "00:50:56:f7:4a:fc",
"iface": "ens33"
"permanent": false,
"expires": "110"
}
]
"""
@@ -91,7 +99,7 @@ import jc.parsers.universal
class info():
version = '1.3'
version = '1.6'
description = 'arp command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -123,7 +131,9 @@ def process(proc_data):
"hwtype": string,
"hwaddress": string,
"flags_mask": string,
"iface": string
"iface": string,
"permanent": boolean,
"expires": integer
}
]
"""
@@ -133,6 +143,14 @@ def process(proc_data):
if 'name' in entry and entry['name'] == '?':
entry['name'] = None
int_list = ['expires']
for key in int_list:
if key in entry:
try:
entry[key] = int(entry[key])
except (ValueError):
entry[key] = None
return proc_data
@@ -153,60 +171,65 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()
raw_output = []
cleandata = list(filter(None, data.splitlines()))
# remove final Entries row if -v was used
if cleandata[-1].startswith('Entries:'):
cleandata.pop(-1)
if jc.utils.has_data(data):
# detect if osx style was used
if cleandata[0][-1] == ']':
raw_output = []
for line in cleandata:
splitline = line.split()
output_line = {
'name': splitline[0],
'address': splitline[1].lstrip('(').rstrip(')'),
'hwtype': splitline[-1].lstrip('[').rstrip(']'),
'hwaddress': splitline[3],
'iface': splitline[5]
}
raw_output.append(output_line)
# remove final Entries row if -v was used
if cleandata[-1].startswith('Entries:'):
cleandata.pop(-1)
if raw:
return raw_output
# detect if freebsd/osx style was used
if cleandata[0][-1] == ']':
for line in cleandata:
splitline = line.split()
output_line = {
'name': splitline[0],
'address': splitline[1].lstrip('(').rstrip(')'),
'hwtype': splitline[-1].lstrip('[').rstrip(']'),
'hwaddress': splitline[3],
'iface': splitline[5]
}
if 'permanent' in splitline:
output_line['permanent'] = True
else:
output_line['permanent'] = False
if 'expires' in splitline:
output_line['expires'] = splitline[-3]
raw_output.append(output_line)
if raw:
return raw_output
else:
return process(raw_output)
# detect if linux style was used
elif cleandata[0].startswith('Address'):
# fix header row to change Flags Mask to flags_mask
cleandata[0] = cleandata[0].replace('Flags Mask', 'flags_mask')
cleandata[0] = cleandata[0].lower()
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
# otherwise, try bsd style
else:
return process(raw_output)
for line in cleandata:
line = line.split()
output_line = {
'name': line[0],
'address': line[1].lstrip('(').rstrip(')'),
'hwtype': line[4].lstrip('[').rstrip(']'),
'hwaddress': line[3],
'iface': line[6],
}
raw_output.append(output_line)
# detect if linux style was used
elif cleandata[0].startswith('Address'):
# fix header row to change Flags Mask to flags_mask
cleandata[0] = cleandata[0].replace('Flags Mask', 'flags_mask')
cleandata[0] = cleandata[0].lower()
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if raw:
return raw_output
else:
return process(raw_output)
# otherwise, try bsd style
if raw:
return raw_output
else:
raw_output = []
for line in cleandata:
line = line.split()
output_line = {
'name': line[0],
'address': line[1].lstrip('(').rstrip(')'),
'hwtype': line[4].lstrip('[').rstrip(']'),
'hwaddress': line[3],
'iface': line[6],
}
raw_output.append(output_line)
if raw:
return raw_output
else:
return process(raw_output)
return process(raw_output)

View File

@@ -79,7 +79,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.2'
description = 'blkid command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -176,7 +176,8 @@ def parse(data, raw=False, quiet=False):
raw_output = []
if data:
if jc.utils.has_data(data):
# if the first field is a device, use normal parsing:
if data.split(maxsplit=1)[0][-1] == ':':
linedata = data.splitlines()

View File

@@ -132,7 +132,7 @@ import jc.parsers.universal
class info():
version = '1.2'
version = '1.4'
description = 'crontab command and file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -225,44 +225,46 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, cleandata))
# Clear any commented lines
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().startswith('#'):
cleandata.pop(i)
if jc.utils.has_data(data):
# Pop any variable assignment lines
cron_var = []
for i, line in reversed(list(enumerate(cleandata))):
if '=' in line:
var_line = cleandata.pop(i)
var_name = var_line.split('=', maxsplit=1)[0].strip()
var_value = var_line.split('=', maxsplit=1)[1].strip()
cron_var.append({'name': var_name,
'value': var_value})
# Clear any commented lines
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().startswith('#'):
cleandata.pop(i)
raw_output['variables'] = cron_var
# Pop any variable assignment lines
cron_var = []
for i, line in reversed(list(enumerate(cleandata))):
if '=' in line:
var_line = cleandata.pop(i)
var_name = var_line.split('=', maxsplit=1)[0].strip()
var_value = var_line.split('=', maxsplit=1)[1].strip()
cron_var.append({'name': var_name,
'value': var_value})
# Pop any shortcut lines
shortcut_list = []
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().startswith('@'):
shortcut_line = cleandata.pop(i)
occurrence = shortcut_line.split(maxsplit=1)[0].strip().lstrip('@')
cmd = shortcut_line.split(maxsplit=1)[1].strip()
shortcut_list.append({'occurrence': occurrence,
'command': cmd})
raw_output['variables'] = cron_var
# Add header row for parsing
cleandata[:0] = ['minute hour day_of_month month day_of_week command']
# Pop any shortcut lines
shortcut_list = []
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().startswith('@'):
shortcut_line = cleandata.pop(i)
occurrence = shortcut_line.split(maxsplit=1)[0].strip().lstrip('@')
cmd = shortcut_line.split(maxsplit=1)[1].strip()
shortcut_list.append({'occurrence': occurrence,
'command': cmd})
if len(cleandata) > 1:
cron_list = jc.parsers.universal.simple_table_parse(cleandata)
# Add header row for parsing
cleandata[:0] = ['minute hour day_of_month month day_of_week command']
raw_output['schedule'] = cron_list
if len(cleandata) > 1:
cron_list = jc.parsers.universal.simple_table_parse(cleandata)
# Add shortcut entries back in
for item in shortcut_list:
raw_output['schedule'].append(item)
raw_output['schedule'] = cron_list
# Add shortcut entries back in
for item in shortcut_list:
raw_output['schedule'].append(item)
if raw:
return raw_output

View File

@@ -133,7 +133,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.3'
description = 'crontab file parser with user support'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -226,46 +226,48 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, cleandata))
# Clear any commented lines
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().startswith('#'):
cleandata.pop(i)
if jc.utils.has_data(data):
# Pop any variable assignment lines
cron_var = []
for i, line in reversed(list(enumerate(cleandata))):
if '=' in line:
var_line = cleandata.pop(i)
var_name = var_line.split('=', maxsplit=1)[0].strip()
var_value = var_line.split('=', maxsplit=1)[1].strip()
cron_var.append({'name': var_name,
'value': var_value})
# Clear any commented lines
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().startswith('#'):
cleandata.pop(i)
raw_output['variables'] = cron_var
# Pop any variable assignment lines
cron_var = []
for i, line in reversed(list(enumerate(cleandata))):
if '=' in line:
var_line = cleandata.pop(i)
var_name = var_line.split('=', maxsplit=1)[0].strip()
var_value = var_line.split('=', maxsplit=1)[1].strip()
cron_var.append({'name': var_name,
'value': var_value})
# Pop any shortcut lines
shortcut_list = []
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().startswith('@'):
shortcut_line = cleandata.pop(i)
occurrence = shortcut_line.split(maxsplit=1)[0].strip().lstrip('@')
usr = shortcut_line.split(maxsplit=2)[1].strip()
cmd = shortcut_line.split(maxsplit=2)[2].strip()
shortcut_list.append({'occurrence': occurrence,
'user': usr,
'command': cmd})
raw_output['variables'] = cron_var
# Add header row for parsing
cleandata[:0] = ['minute hour day_of_month month day_of_week user command']
# Pop any shortcut lines
shortcut_list = []
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().startswith('@'):
shortcut_line = cleandata.pop(i)
occurrence = shortcut_line.split(maxsplit=1)[0].strip().lstrip('@')
usr = shortcut_line.split(maxsplit=2)[1].strip()
cmd = shortcut_line.split(maxsplit=2)[2].strip()
shortcut_list.append({'occurrence': occurrence,
'user': usr,
'command': cmd})
if len(cleandata) > 1:
cron_list = jc.parsers.universal.simple_table_parse(cleandata)
# Add header row for parsing
cleandata[:0] = ['minute hour day_of_month month day_of_week user command']
raw_output['schedule'] = cron_list
if len(cleandata) > 1:
cron_list = jc.parsers.universal.simple_table_parse(cleandata)
# Add shortcut entries back in
for item in shortcut_list:
raw_output['schedule'].append(item)
raw_output['schedule'] = cron_list
# Add shortcut entries back in
for item in shortcut_list:
raw_output['schedule'].append(item)
if raw:
return raw_output

View File

@@ -63,7 +63,7 @@ import csv
class info():
version = '1.0'
version = '1.1'
description = 'CSV file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -123,7 +123,8 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, cleandata))
if cleandata:
if jc.utils.has_data(data):
dialect = None
try:
dialect = csv.Sniffer().sniff(data[:1024])

View File

@@ -6,7 +6,7 @@ Usage:
Compatibility:
'linux', 'darwin'
'linux', 'darwin', 'freebsd'
Examples:
@@ -73,13 +73,13 @@ import jc.parsers.universal
class info():
version = '1.2'
version = '1.5'
description = 'df command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux', 'darwin']
compatible = ['linux', 'darwin', 'freebsd']
magic_commands = ['df']
@@ -184,14 +184,17 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()
raw_output = []
# fix headers
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace('-', '_')
cleandata[0] = cleandata[0].replace('mounted on', 'mounted_on')
if jc.utils.has_data(data):
# parse the data
raw_output = jc.parsers.universal.sparse_table_parse(cleandata)
# fix headers
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace('-', '_')
cleandata[0] = cleandata[0].replace('mounted on', 'mounted_on')
# parse the data
raw_output = jc.parsers.universal.sparse_table_parse(cleandata)
if raw:
return raw_output

View File

@@ -324,7 +324,7 @@ import jc.utils
class info():
version = '1.2'
version = '1.3'
description = 'dig command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -574,100 +574,103 @@ def parse(data, raw=False, quiet=False):
axfr = False
output_entry = {}
for line in cleandata:
if line.startswith('; <<>> ') and ' axfr ' in line.lower():
question = False
authority = False
answer = False
axfr = True
axfr_list = []
continue
if jc.utils.has_data(data):
for line in cleandata:
if ';' not in line and axfr:
axfr_list.append(parse_axfr(line))
output_entry.update({'axfr': axfr_list})
continue
if line.startswith('; <<>> ') and ' axfr ' in line.lower():
question = False
authority = False
answer = False
axfr = True
axfr_list = []
continue
if line.startswith(';; ->>HEADER<<-'):
output_entry = {}
output_entry.update(parse_header(line))
continue
if ';' not in line and axfr:
axfr_list.append(parse_axfr(line))
output_entry.update({'axfr': axfr_list})
continue
if line.startswith(';; flags:'):
output_entry.update(parse_flags_line(line))
continue
if line.startswith(';; ->>HEADER<<-'):
output_entry = {}
output_entry.update(parse_header(line))
continue
if line.startswith(';; QUESTION SECTION:'):
question = True
authority = False
answer = False
axfr = False
continue
if line.startswith(';; flags:'):
output_entry.update(parse_flags_line(line))
continue
if question:
output_entry['question'] = parse_question(line)
question = False
authority = False
answer = False
axfr = False
continue
if line.startswith(';; QUESTION SECTION:'):
question = True
authority = False
answer = False
axfr = False
continue
if line.startswith(';; AUTHORITY SECTION:'):
question = False
authority = True
answer = False
axfr = False
authority_list = []
continue
if question:
output_entry['question'] = parse_question(line)
question = False
authority = False
answer = False
axfr = False
continue
if ';' not in line and authority:
authority_list.append(parse_authority(line))
output_entry.update({'authority': authority_list})
continue
if line.startswith(';; AUTHORITY SECTION:'):
question = False
authority = True
answer = False
axfr = False
authority_list = []
continue
if line.startswith(';; ANSWER SECTION:'):
question = False
authority = False
answer = True
axfr = False
answer_list = []
continue
if ';' not in line and authority:
authority_list.append(parse_authority(line))
output_entry.update({'authority': authority_list})
continue
if ';' not in line and answer:
answer_list.append(parse_answer(line))
output_entry.update({'answer': answer_list})
continue
if line.startswith(';; ANSWER SECTION:'):
question = False
authority = False
answer = True
axfr = False
answer_list = []
continue
# footer consists of 4 lines
# footer line 1
if line.startswith(';; Query time:'):
output_entry.update({'query_time': line.split(':')[1].lstrip()})
continue
if ';' not in line and answer:
answer_list.append(parse_answer(line))
output_entry.update({'answer': answer_list})
continue
# footer line 2
if line.startswith(';; SERVER:'):
output_entry.update({'server': line.split(':')[1].lstrip()})
continue
# footer consists of 4 lines
# footer line 1
if line.startswith(';; Query time:'):
output_entry.update({'query_time': line.split(':')[1].lstrip()})
continue
# footer line 3
if line.startswith(';; WHEN:'):
output_entry.update({'when': line.split(':', maxsplit=1)[1].lstrip()})
continue
# footer line 2
if line.startswith(';; SERVER:'):
output_entry.update({'server': line.split(':')[1].lstrip()})
continue
# footer line 4 (last line)
if line.startswith(';; MSG SIZE rcvd:'):
output_entry.update({'rcvd': line.split(':')[1].lstrip()})
# footer line 3
if line.startswith(';; WHEN:'):
output_entry.update({'when': line.split(':', maxsplit=1)[1].lstrip()})
continue
if output_entry:
raw_output.append(output_entry)
elif line.startswith(';; XFR size:'):
output_entry.update({'size': line.split(':')[1].lstrip()})
# footer line 4 (last line)
if line.startswith(';; MSG SIZE rcvd:'):
output_entry.update({'rcvd': line.split(':')[1].lstrip()})
if output_entry:
raw_output.append(output_entry)
if output_entry:
raw_output.append(output_entry)
elif line.startswith(';; XFR size:'):
output_entry.update({'size': line.split(':')[1].lstrip()})
if output_entry:
raw_output.append(output_entry)
raw_output = list(filter(None, raw_output))
raw_output = list(filter(None, raw_output))
if raw:
return raw_output
else:

341
jc/parsers/dmidecode.py Normal file
View File

@@ -0,0 +1,341 @@
"""jc - JSON CLI output utility dmidecode Parser
Usage:
specify --dmidecode as the first argument if the piped input is coming from dmidecode
Compatibility:
'linux'
Examples:
# dmidecode | jc --dmidecode -p
[
{
"handle": "0x0000",
"type": 0,
"bytes": 24,
"description": "BIOS Information",
"values": {
"vendor": "Phoenix Technologies LTD",
"version": "6.00",
"release_date": "04/13/2018",
"address": "0xEA490",
"runtime_size": "88944 bytes",
"rom_size": "64 kB",
"characteristics": [
"ISA is supported",
"PCI is supported",
"PC Card (PCMCIA) is supported",
"PNP is supported",
"APM is supported",
"BIOS is upgradeable",
"BIOS shadowing is allowed",
"ESCD support is available",
"Boot from CD is supported",
"Selectable boot is supported",
"EDD is supported",
"Print screen service is supported (int 5h)",
"8042 keyboard services are supported (int 9h)",
"Serial services are supported (int 14h)",
"Printer services are supported (int 17h)",
"CGA/mono video services are supported (int 10h)",
"ACPI is supported",
"Smart battery is supported",
"BIOS boot specification is supported",
"Function key-initiated network boot is supported",
"Targeted content distribution is supported"
],
"bios_revision": "4.6",
"firmware_revision": "0.0"
}
},
...
]
# dmidecode | jc --dmidecode -p -r
[
{
"handle": "0x0000",
"type": "0",
"bytes": "24",
"description": "BIOS Information",
"values": {
"vendor": "Phoenix Technologies LTD",
"version": "6.00",
"release_date": "04/13/2018",
"address": "0xEA490",
"runtime_size": "88944 bytes",
"rom_size": "64 kB",
"characteristics": [
"ISA is supported",
"PCI is supported",
"PC Card (PCMCIA) is supported",
"PNP is supported",
"APM is supported",
"BIOS is upgradeable",
"BIOS shadowing is allowed",
"ESCD support is available",
"Boot from CD is supported",
"Selectable boot is supported",
"EDD is supported",
"Print screen service is supported (int 5h)",
"8042 keyboard services are supported (int 9h)",
"Serial services are supported (int 14h)",
"Printer services are supported (int 17h)",
"CGA/mono video services are supported (int 10h)",
"ACPI is supported",
"Smart battery is supported",
"BIOS boot specification is supported",
"Function key-initiated network boot is supported",
"Targeted content distribution is supported"
],
"bios_revision": "4.6",
"firmware_revision": "0.0"
}
},
...
]
"""
import jc.utils
class info():
version = '1.1'
description = 'dmidecode command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
# details = 'enter any other details here'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux']
magic_commands = ['dmidecode']
__version__ = info.version
def process(proc_data):
"""
Final processing to conform to the schema.
Parameters:
proc_data: (dictionary) raw structured data to process
Returns:
List of dictionaries. Structured data with the following schema:
[
{
"handle": string,
"type": integer,
"bytes": integer,
"description": string,
"values": { (null if empty)
"lowercase_no_spaces_keys": string,
"multiline_key_values": [
string,
]
}
}
]
"""
for entry in proc_data:
int_list = ['type', 'bytes']
for key in int_list:
if key in entry:
try:
key_int = int(entry[key])
entry[key] = key_int
except (ValueError):
entry[key] = None
if not entry['values']:
entry['values'] = None
return proc_data
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) output preprocessed JSON if True
quiet: (boolean) suppress warning messages if True
Returns:
List of dictionaries. Raw or processed structured data.
"""
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
item_header = False
item_values = False
value_list = False
item = None
header = None
key = None
val = None
attribute = None
values = None
key_data = None
raw_output = []
if jc.utils.has_data(data):
data = data.splitlines()
# remove header rows
for row in data.copy():
if row:
data.pop(0)
else:
break
# main parsing loop
for line in data:
# new item
if not line:
item_header = True
item_values = False
value_list = False
if item:
if values:
item['values'][attribute] = values
if key_data:
item['values'][f'{key}_data'] = key_data
raw_output.append(item)
item = {}
header = None
key = None
val = None
attribute = None
values = []
key_data = []
continue
# header
if line.startswith('Handle ') and line.endswith('bytes'):
# Handle 0x0000, DMI type 0, 24 bytes
header = line.replace(',', ' ').split()
item = {
'handle': header[1],
'type': header[4],
'bytes': header[5]
}
continue
# description
if item_header:
item_header = False
item_values = True
value_list = False
item['description'] = line
item['values'] = {}
continue
# new item if multiple descriptions in handle
if not item_header and not line.startswith('\t'):
item_header = False
item_values = True
value_list = False
if item:
if values:
item['values'][attribute] = values
if key_data:
item['values'][f'{key}_data'] = key_data
raw_output.append(item)
item = {
'handle': header[1],
'type': header[4],
'bytes': header[5],
'description': line,
'values': {}
}
key = None
val = None
attribute = None
values = []
key_data = []
continue
# keys and values
if item_values \
and len(line.split(':', maxsplit=1)) == 2 \
and line.startswith('\t') \
and not line.startswith('\t\t') \
and not line.strip().endswith(':'):
item_header = False
item_values = True
value_list = False
if values:
item['values'][attribute] = values
values = []
if key_data:
item['values'][f'{key}_data'] = key_data
key_data = []
key = line.split(':', maxsplit=1)[0].strip().lower().replace(' ', '_')
val = line.split(':', maxsplit=1)[1].strip()
item['values'].update({key: val})
continue
# multi-line key
if item_values \
and line.startswith('\t') \
and not line.startswith('\t\t') \
and line.strip().endswith(':'):
item_header = False
item_values = True
value_list = True
if values:
item['values'][attribute] = values
values = []
if key_data:
item['values'][f'{key}_data'] = key_data
key_data = []
attribute = line[:-1].strip().lower().replace(' ', '_')
values = []
continue
# multi-line values
if value_list \
and line.startswith('\t\t'):
values.append(line.strip())
continue
# data for hybrid multi-line objects
if item_values \
and not value_list \
and line.startswith('\t\t'):
if f'{key}_data' not in item['values']:
item['values'][f'{key}_data'] = []
key_data.append(line.strip())
continue
if item:
raw_output.append(item)
if raw:
return raw_output
else:
return process(raw_output)

View File

@@ -73,7 +73,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.2'
description = 'du command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -137,12 +137,12 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = []
cleandata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
cleandata.insert(0, 'size name')
raw_output = jc.parsers.universal.simple_table_parse(cleandata)

View File

@@ -52,7 +52,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.2'
description = 'env command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -116,12 +116,10 @@ def parse(data, raw=False, quiet=False):
raw_output = {}
linedata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, linedata))
cleandata = list(filter(None, data.splitlines()))
if cleandata:
if jc.utils.has_data(data):
for entry in cleandata:
parsed_line = entry.split('=', maxsplit=1)

View File

@@ -48,7 +48,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.2'
description = 'file command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -104,23 +104,26 @@ def parse(data, raw=False, quiet=False):
raw_output = []
warned = False
for line in filter(None, data.splitlines()):
linedata = line.rsplit(': ', maxsplit=1)
try:
filename = linedata[0].strip()
filetype = linedata[1].strip()
if jc.utils.has_data(data):
raw_output.append(
{
'filename': filename,
'type': filetype
}
)
except IndexError:
if not warned:
jc.utils.warning_message('Filenames with newline characters detected. Some filenames may be truncated.')
warned = True
for line in filter(None, data.splitlines()):
linedata = line.rsplit(': ', maxsplit=1)
try:
filename = linedata[0].strip()
filetype = linedata[1].strip()
raw_output.append(
{
'filename': filename,
'type': filetype
}
)
except IndexError:
if not warned:
jc.utils.warning_message('Filenames with newline characters detected. Some filenames may be truncated.')
warned = True
if raw:
return raw_output

View File

@@ -78,9 +78,11 @@ def parse(data, raw=False, quiet=False):
raw_output = []
for line in filter(None, data.splitlines()):
# parse the content
pass
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
# parse the content
pass
if raw:
return raw_output

View File

@@ -53,7 +53,7 @@ import jc.parsers.universal
class info():
version = '1.0'
version = '1.2'
description = 'free command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -122,14 +122,18 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace('buff/cache', 'buff_cache')
cleandata[0] = 'type ' + cleandata[0]
raw_output = []
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if jc.utils.has_data(data):
for entry in raw_output:
entry['type'] = entry['type'].rstrip(':')
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace('buff/cache', 'buff_cache')
cleandata[0] = 'type ' + cleandata[0]
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
for entry in raw_output:
entry['type'] = entry['type'].rstrip(':')
if raw:
return raw_output

View File

@@ -6,7 +6,7 @@ Usage:
Compatibility:
'linux'
'linux', 'freebsd'
Examples:
@@ -70,13 +70,13 @@ import jc.utils
class info():
version = '1.1'
version = '1.3'
description = 'fstab file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux']
compatible = ['linux', 'freebsd']
__version__ = info.version
@@ -141,7 +141,8 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, cleandata))
if cleandata:
if jc.utils.has_data(data):
for line in cleandata:
output_line = {}
# ignore commented lines

View File

@@ -94,7 +94,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = '/etc/group file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -169,7 +169,8 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, cleandata))
if cleandata:
if jc.utils.has_data(data):
for entry in cleandata:
if entry.startswith('#'):
continue

View File

@@ -60,7 +60,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = '/etc/gshadow file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -131,7 +131,8 @@ def parse(data, raw=False, quiet=False):
# Clear any blank lines
cleandata = list(filter(None, cleandata))
if cleandata:
if jc.utils.has_data(data):
for entry in cleandata:
if entry.startswith('#'):
continue

View File

@@ -44,7 +44,7 @@ import jc.utils
class info():
version = '1.2'
version = '1.3'
description = 'history command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -108,17 +108,19 @@ def parse(data, raw=False, quiet=False):
raw_output = {}
# split lines and clear out any non-ascii chars
linedata = data.encode('ascii', errors='ignore').decode().splitlines()
if jc.utils.has_data(data):
# Skip any blank lines
for entry in filter(None, linedata):
try:
parsed_line = entry.split(maxsplit=1)
raw_output[parsed_line[0]] = parsed_line[1]
except IndexError:
# need to catch indexerror in case there is weird input from prior commands
pass
# split lines and clear out any non-ascii chars
linedata = data.encode('ascii', errors='ignore').decode().splitlines()
# Skip any blank lines
for entry in filter(None, linedata):
try:
parsed_line = entry.split(maxsplit=1)
raw_output[parsed_line[0]] = parsed_line[1]
except IndexError:
# need to catch indexerror in case there is weird input from prior commands
pass
if raw:
return raw_output

View File

@@ -61,7 +61,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.2'
description = '/etc/hosts file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -117,12 +117,12 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = []
cleandata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
for line in cleandata:
output_line = {}
# ignore commented lines

View File

@@ -70,7 +70,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'id command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -166,12 +166,12 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = {}
cleandata = data.split()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.split()))
if jc.utils.has_data(data):
if cleandata:
for section in cleandata:
if section.startswith('uid'):
uid_parsed = section.replace('(', '=').replace(')', '=')

View File

@@ -147,7 +147,7 @@ import jc.utils
class info():
version = '1.7'
version = '1.8'
description = 'ifconfig command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -475,14 +475,16 @@ def parse(data, raw=False, quiet=False):
raw_output = []
parsed = IfconfigParser(console_output=data)
interfaces = parsed.get_interfaces()
if jc.utils.has_data(data):
# convert ifconfigparser output to a dictionary
for iface in interfaces:
d = interfaces[iface]._asdict()
dct = dict(d)
raw_output.append(dct)
parsed = IfconfigParser(console_output=data)
interfaces = parsed.get_interfaces()
# convert ifconfigparser output to a dictionary
for iface in interfaces:
d = interfaces[iface]._asdict()
dct = dict(d)
raw_output.append(dct)
if raw:
return raw_output

View File

@@ -47,7 +47,7 @@ import configparser
class info():
version = '1.0'
version = '1.1'
description = 'INI file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -101,7 +101,8 @@ def parse(data, raw=False, quiet=False):
raw_output = {}
if data:
if jc.utils.has_data(data):
ini = configparser.ConfigParser()
ini.read_string(data)
raw_output = {s: dict(ini.items(s)) for s in ini.sections()}

View File

@@ -134,7 +134,7 @@ import jc.utils
class info():
version = '1.2'
version = '1.4'
description = 'iptables command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -194,19 +194,19 @@ def process(proc_data):
if 'bytes' in rule:
multiplier = 1
if rule['bytes'][-1] == 'K':
multiplier = 1000
multiplier = 10 ** 3
rule['bytes'] = rule['bytes'].rstrip('K')
elif rule['bytes'][-1] == 'M':
multiplier = 1000000
multiplier = 10 ** 6
rule['bytes'] = rule['bytes'].rstrip('M')
elif rule['bytes'][-1] == 'G':
multiplier = 1000000000
multiplier = 10 ** 9
rule['bytes'] = rule['bytes'].rstrip('G')
elif rule['bytes'][-1] == 'T':
multiplier = 1000000000000
multiplier = 10 ** 12
rule['bytes'] = rule['bytes'].rstrip('T')
elif rule['bytes'][-1] == 'P':
multiplier = 1000000000000000
multiplier = 10 ** 15
rule['bytes'] = rule['bytes'].rstrip('P')
try:
@@ -243,36 +243,39 @@ def parse(data, raw=False, quiet=False):
chain = {}
headers = []
cleandata = data.splitlines()
if jc.utils.has_data(data):
for line in cleandata:
for line in list(filter(None, data.splitlines())):
if line.startswith('Chain'):
if line.startswith('Chain'):
if chain:
raw_output.append(chain)
chain = {}
headers = []
parsed_line = line.split()
chain['chain'] = parsed_line[1]
chain['rules'] = []
continue
elif line.startswith('target') or line.find('pkts') == 1 or line.startswith('num'):
headers = []
headers = [h for h in ' '.join(line.lower().strip().split()).split() if h]
headers.append("options")
continue
else:
rule = line.split(maxsplit=len(headers) - 1)
temp_rule = dict(zip(headers, rule))
if temp_rule:
chain['rules'].append(temp_rule)
if chain:
raw_output.append(chain)
chain = {}
headers = []
parsed_line = line.split()
chain['chain'] = parsed_line[1]
chain['rules'] = []
continue
elif line.startswith('target') or line.find('pkts') == 1 or line.startswith('num'):
headers = []
headers = [h for h in ' '.join(line.lower().strip().split()).split() if h]
headers.append("options")
continue
else:
rule = line.split(maxsplit=len(headers) - 1)
temp_rule = dict(zip(headers, rule))
if temp_rule:
chain['rules'].append(temp_rule)
raw_output = list(filter(None, raw_output))
if raw:
return raw_output

View File

@@ -77,7 +77,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.2'
description = 'jobs command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -144,12 +144,10 @@ def parse(data, raw=False, quiet=False):
raw_output = []
linedata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, linedata))
cleandata = list(filter(None, data.splitlines()))
if cleandata:
if jc.utils.has_data(data):
for entry in cleandata:
output_line = {}

View File

@@ -72,7 +72,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.3'
description = 'last and lastb command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -110,6 +110,9 @@ def process(proc_data):
]
"""
for entry in proc_data:
if 'user' in entry and entry['user'] == 'boot_time':
entry['user'] = 'boot time'
if 'tty' in entry and entry['tty'] == '~':
entry['tty'] = None
@@ -146,19 +149,20 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = []
cleandata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
for entry in cleandata:
output_line = {}
if entry.startswith('wtmp begins ') or entry.startswith('btmp begins '):
if entry.startswith('wtmp begins ') or entry.startswith('btmp begins ') or entry.startswith('utx.log begins '):
continue
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')
@@ -166,6 +170,11 @@ def parse(data, raw=False, quiet=False):
if re.match(r'[MTWFS][ouerha][nedritnu] [JFMASOND][aepuco][nbrynlgptvc]', ' '.join(linedata[2:4])):
linedata.insert(2, '-')
# 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]

View File

@@ -149,7 +149,7 @@ import jc.utils
class info():
version = '1.4'
version = '1.6'
description = 'ls command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -226,20 +226,20 @@ def parse(data, raw=False, quiet=False):
linedata = data.splitlines()
# Delete first line if it starts with 'total 1234'
if linedata:
if jc.utils.has_data(data):
# Delete first line if it starts with 'total 1234'
if re.match(r'total [0-9]+', linedata[0]):
linedata.pop(0)
# Look for parent line if glob or -R is used
if not re.match(r'[-dclpsbDCMnP?]([-r][-w][-xsS]){2}([-r][-w][-xtT])[+]?', linedata[0]) \
and linedata[0].endswith(':'):
parent = linedata.pop(0)[:-1]
# Pop following total line if it exists
if re.match(r'total [0-9]+', linedata[0]):
linedata.pop(0)
# Look for parent line if glob or -R is used
if not re.match(r'[-dclpsbDCMnP?]([-r][-w][-xsS]){2}([-r][-w][-xtT])[+]?', linedata[0]) \
and linedata[0].endswith(':'):
parent = linedata.pop(0)[:-1]
# Pop following total line if it exists
if re.match(r'total [0-9]+', linedata[0]):
linedata.pop(0)
if linedata:
# Check if -l was used to parse extra data
if re.match(r'[-dclpsbDCMnP?]([-r][-w][-xsS]){2}([-r][-w][-xtT])[+]?', linedata[0]):
for entry in linedata:

View File

@@ -216,7 +216,7 @@ import jc.parsers.universal
class info():
version = '1.3'
version = '1.5'
description = 'lsblk command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -327,20 +327,23 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
linedata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, linedata))
cleandata = data.splitlines()
cleandata = list(filter(None, data.splitlines()))
raw_output = []
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace(':', '_')
cleandata[0] = cleandata[0].replace('-', '_')
if jc.utils.has_data(data):
raw_output = jc.parsers.universal.sparse_table_parse(cleandata)
cleandata = data.splitlines()
# clean up non-ascii characters, if any
for entry in raw_output:
entry['name'] = entry['name'].encode('ascii', errors='ignore').decode()
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace(':', '_')
cleandata[0] = cleandata[0].replace('-', '_')
raw_output = jc.parsers.universal.sparse_table_parse(cleandata)
# clean up non-ascii characters, if any
for entry in raw_output:
entry['name'] = entry['name'].encode('ascii', errors='ignore').decode()
if raw:
return raw_output

View File

@@ -107,7 +107,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.3'
description = 'lsmod command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -175,13 +175,17 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()
cleandata[0] = cleandata[0].lower()
raw_output = []
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if jc.utils.has_data(data):
for mod in raw_output:
if 'by' in mod:
mod['by'] = mod['by'].split(',')
cleandata[0] = cleandata[0].lower()
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
for mod in raw_output:
if 'by' in mod:
mod['by'] = mod['by'].split(',')
if raw:
return raw_output

View File

@@ -97,7 +97,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.2'
description = 'lsof command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -169,12 +169,11 @@ def parse(data, raw=False, quiet=False):
raw_output = []
linedata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, linedata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
cleandata[0] = cleandata[0].lower()
cleandata[0] = cleandata[0].replace('/', '_')

View File

@@ -6,7 +6,7 @@ Usage:
Compatibility:
'linux', 'darwin'
'linux', 'darwin', 'freebsd'
Example:
@@ -56,13 +56,13 @@ import jc.utils
class info():
version = '1.2'
version = '1.5'
description = 'mount command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux', 'darwin']
compatible = ['linux', 'darwin', 'freebsd']
magic_commands = ['mount']
@@ -158,12 +158,12 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
linedata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, linedata))
cleandata = list(filter(None, data.splitlines()))
raw_output = []
if jc.utils.has_data(data):
if cleandata:
# check for OSX output
if ' type ' not in cleandata[0]:
raw_output = osx_parse(cleandata)

View File

@@ -4,13 +4,18 @@ Usage:
Specify --netstat as the first argument if the piped input is coming from netstat
Caveats:
- Use of multiple 'l' options is not supported on OSX (e.g. 'netstat -rlll')
- Use of the 'A' option is not supported on OSX when using the 'r' option (e.g. netstat -rA)
Compatibility:
'linux'
'linux', 'darwin', 'freebsd'
Examples:
$ sudo netstat -apee | jc --netstat -p
# netstat -apee | jc --netstat -p
[
{
"proto": "tcp",
@@ -160,166 +165,95 @@ Examples:
...
]
$ sudo netstat -apee | jc --netstat -p -r
$ netstat -r | jc --netstat -p
[
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "0.0.0.0",
"state": "LISTEN",
"user": "systemd-resolve",
"inode": "26958",
"program_name": "systemd-resolve",
"kind": "network",
"pid": "887",
"local_port": "domain",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
"destination": "default",
"gateway": "gateway",
"genmask": "0.0.0.0",
"route_flags": "UG",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "ens33",
"kind": "route",
"route_flags_pretty": [
"UP",
"GATEWAY"
]
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "0.0.0.0",
"foreign_address": "0.0.0.0",
"state": "LISTEN",
"user": "root",
"inode": "30499",
"program_name": "sshd",
"kind": "network",
"pid": "1186",
"local_port": "ssh",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
"destination": "172.17.0.0",
"gateway": "0.0.0.0",
"genmask": "255.255.0.0",
"route_flags": "U",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "docker0",
"kind": "route",
"route_flags_pretty": [
"UP"
]
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "localhost",
"state": "ESTABLISHED",
"user": "root",
"inode": "46829",
"program_name": "sshd: root",
"kind": "network",
"pid": "2242",
"local_port": "ssh",
"foreign_port": "52186",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
"destination": "192.168.71.0",
"gateway": "0.0.0.0",
"genmask": "255.255.255.0",
"route_flags": "U",
"mss": 0,
"window": 0,
"irtt": 0,
"iface": "ens33",
"kind": "route",
"route_flags_pretty": [
"UP"
]
}
]
$ netstat -i | jc --netstat -p
[
{
"iface": "ens33",
"mtu": 1500,
"rx_ok": 476,
"rx_err": 0,
"rx_drp": 0,
"rx_ovr": 0,
"tx_ok": 312,
"tx_err": 0,
"tx_drp": 0,
"tx_ovr": 0,
"flg": "BMRU",
"kind": "interface"
},
{
"proto": "tcp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "localhost",
"state": "ESTABLISHED",
"user": "root",
"inode": "46828",
"program_name": "ssh",
"kind": "network",
"pid": "2241",
"local_port": "52186",
"foreign_port": "ssh",
"transport_protocol": "tcp",
"network_protocol": "ipv4"
},
{
"proto": "tcp6",
"recv_q": "0",
"send_q": "0",
"local_address": "[::]",
"foreign_address": "[::]",
"state": "LISTEN",
"user": "root",
"inode": "30510",
"program_name": "sshd",
"kind": "network",
"pid": "1186",
"local_port": "ssh",
"foreign_port": "*",
"transport_protocol": "tcp",
"network_protocol": "ipv6"
},
{
"proto": "udp",
"recv_q": "0",
"send_q": "0",
"local_address": "localhost",
"foreign_address": "0.0.0.0",
"state": null,
"user": "systemd-resolve",
"inode": "26957",
"program_name": "systemd-resolve",
"kind": "network",
"pid": "887",
"local_port": "domain",
"foreign_port": "*",
"transport_protocol": "udp",
"network_protocol": "ipv4"
},
{
"proto": "raw6",
"recv_q": "0",
"send_q": "0",
"local_address": "[::]",
"foreign_address": "[::]",
"state": "7",
"user": "systemd-network",
"inode": "27001",
"program_name": "systemd-network",
"kind": "network",
"pid": "867",
"local_port": "ipv6-icmp",
"foreign_port": "*",
"transport_protocol": null,
"network_protocol": "ipv6"
},
{
"proto": "unix",
"refcnt": "2",
"flags": null,
"type": "DGRAM",
"state": null,
"inode": "33322",
"program_name": "systemd",
"path": "/run/user/1000/systemd/notify",
"kind": "socket",
"pid": " 1607"
},
{
"proto": "unix",
"refcnt": "2",
"flags": "ACC",
"type": "SEQPACKET",
"state": "LISTENING",
"inode": "20835",
"program_name": "init",
"path": "/run/udev/control",
"kind": "socket",
"pid": " 1"
},
...
"iface": "lo",
"mtu": 65536,
"rx_ok": 0,
"rx_err": 0,
"rx_drp": 0,
"rx_ovr": 0,
"tx_ok": 0,
"tx_err": 0,
"tx_drp": 0,
"tx_ovr": 0,
"flg": "LRU",
"kind": "interface"
}
]
"""
import string
import jc.utils
class info():
version = '1.4'
version = '1.8'
description = 'netstat command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux']
compatible = ['linux', 'darwin', 'freebsd']
magic_commands = ['netstat']
@@ -340,34 +274,112 @@ def process(proc_data):
[
{
"proto": string,
"recv_q": integer,
"send_q": integer,
"transport_protocol" string,
"network_protocol": string,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"foreign_address": string,
"foreign_port": string,
"foreign_port_num": integer,
"state": string,
"program_name": string,
"pid": integer,
"user": string,
"security_context": string,
"refcnt": integer,
"flags": string,
"type": string,
"inode": integer,
"path": string,
"kind": string
"proto": string,
"recv_q": integer,
"send_q": integer,
"transport_protocol" string,
"network_protocol": string,
"local_address": string,
"local_port": string,
"local_port_num": integer,
"foreign_address": string,
"foreign_port": string,
"foreign_port_num": integer,
"state": string,
"program_name": string,
"pid": integer,
"user": string,
"security_context": string,
"refcnt": integer,
"flags": string,
"type": string,
"inode": integer,
"path": string,
"kind": string,
"address": string,
"unix_inode": string,
"conn": string,
"refs": string,
"nextref": string,
"name": string,
"unit": integer,
"vendor": integer,
"class": integer,
"subcla": integer,
"unix_flags": integer,
"pcbcount": integer,
"rcvbuf": integer,
"sndbuf": integer,
"rxbytes": integer,
"txbytes": integer,
"destination": string,
"gateway": string,
"route_flags": string,
"route_flags_pretty": [
string,
]
"route_refs": integer,
"use": integer,
"mtu": integer,
"expire": string,
"genmask": string,
"mss": integer,
"window": integer,
"irtt": integer,
"iface": string,
"metric": integer,
"network": string,
"address": string,
"ipkts": integer, - = null
"ierrs": integer, - = null
"idrop": integer, - = null
"opkts": integer, - = null
"oerrs": integer, - = null
"coll": integer, - = null
"rx_ok": integer,
"rx_err": integer,
"rx_drp": integer,
"rx_ovr": integer,
"tx_ok": integer,
"tx_err": integer,
"tx_drp": integer,
"tx_ovr": integer,
"flg": string,
"ibytes": integer,
"obytes": integer,
"r_mbuf": integer,
"s_mbuf": integer,
"r_clus": integer,
"s_clus": integer,
"r_hiwa": integer,
"s_hiwa": integer,
"r_lowa": integer,
"s_lowa": integer,
"r_bcnt": integer,
"s_bcnt": integer,
"r_bmax": integer,
"s_bmax": integer,
"rexmit": integer,
"ooorcv": integer,
"0_win": integer,
"rexmt": float,
"persist": float,
"keep": float,
"2msl": float,
"delack": float,
"rcvtime": float,
}
]
"""
for entry in proc_data:
# integer changes
int_list = ['recv_q', 'send_q', 'pid', 'refcnt', 'inode']
int_list = ['recv_q', 'send_q', 'pid', 'refcnt', 'inode', 'unit', 'vendor', 'class',
'osx_flags', 'subcla', 'pcbcount', 'rcvbuf', 'sndbuf', 'rxbytes', 'txbytes',
'route_refs', 'use', 'mtu', 'mss', 'window', 'irtt', 'metric', 'ipkts',
'ierrs', 'opkts', 'oerrs', 'coll', 'rx_ok', 'rx_err', 'rx_drp', 'rx_ovr',
'tx_ok', 'tx_err', 'tx_drp', 'tx_ovr', 'idrop', 'ibytes', 'obytes', 'r_mbuf',
's_mbuf', 'r_clus', 's_clus', 'r_hiwa', 's_hiwa', 'r_lowa', 's_lowa', 'r_bcnt',
's_bcnt', 'r_bmax', 's_bmax', 'rexmit', 'ooorcv', '0_win']
for key in int_list:
if key in entry:
try:
@@ -376,6 +388,16 @@ def process(proc_data):
except (ValueError):
entry[key] = None
# float changes
float_list = ['rexmt', 'persist', 'keep', '2msl', 'delack', 'rcvtime']
for key in float_list:
if key in entry:
try:
key_float = float(entry[key])
entry[key] = key_float
except (ValueError):
entry[key] = None
if 'local_port' in entry:
try:
entry['local_port_num'] = int(entry['local_port'])
@@ -391,128 +413,6 @@ def process(proc_data):
return proc_data
def normalize_headers(header):
header = header.lower()
header = header.replace('local address', 'local_address')
header = header.replace('foreign address', 'foreign_address')
header = header.replace('pid/program name', 'program_name')
header = header.replace('security context', 'security_context')
header = header.replace('i-node', 'inode')
header = header.replace('-', '_')
return header
def parse_network(headers, entry):
# Count words in header
# if len of line is one less than len of header, then insert None in field 5
entry = entry.split(maxsplit=len(headers) - 1)
if len(entry) == len(headers) - 1:
entry.insert(5, None)
output_line = dict(zip(headers, entry))
output_line['kind'] = 'network'
return output_line
def parse_socket(header_text, headers, entry):
output_line = {}
# get the column # of first letter of "state"
state_col = header_text.find('state')
# get the program name column area
pn_start = header_text.find('program_name')
pn_end = header_text.find('path') - 1
# remove [ and ] from each line
entry = entry.replace('[ ]', '---')
entry = entry.replace('[', ' ').replace(']', ' ')
# find program_name column area and substitute spaces with \u2063 there
old_pn = entry[pn_start:pn_end]
new_pn = old_pn.replace(' ', '\u2063')
entry = entry.replace(old_pn, new_pn)
entry_list = entry.split(maxsplit=len(headers) - 1)
# check column # to see if state column is populated
if entry[state_col] in string.whitespace:
entry_list.insert(4, None)
output_line = dict(zip(headers, entry_list))
output_line['kind'] = 'socket'
# fix program_name field to turn \u2063 back to spaces
if 'program_name' in output_line:
if output_line['program_name']:
old_d_pn = output_line['program_name']
new_d_pn = old_d_pn.replace('\u2063', ' ')
output_line['program_name'] = new_d_pn
return output_line
def parse_post(raw_data):
# clean up trailing whitespace on each item in each entry
# flags --- = null
# program_name - = null
# split pid and program name and ip addresses and ports
# create network and transport protocol fields
for entry in raw_data:
for item in entry:
try:
entry[item] = entry[item].rstrip()
except (AttributeError):
# skips trying to rstrip Null entries
pass
if 'flags' in entry:
if entry['flags'] == '---':
entry['flags'] = None
if 'program_name' in entry:
entry['program_name'] = entry['program_name'].strip()
if entry['program_name'] == '-':
entry['program_name'] = None
if entry['program_name']:
pid = entry['program_name'].split('/', maxsplit=1)[0]
name = entry['program_name'].split('/', maxsplit=1)[1]
entry['pid'] = pid
entry['program_name'] = name
if 'local_address' in entry:
if entry['local_address']:
ladd = entry['local_address'].rsplit(':', maxsplit=1)[0]
lport = entry['local_address'].rsplit(':', maxsplit=1)[1]
entry['local_address'] = ladd
entry['local_port'] = lport
if 'foreign_address' in entry:
if entry['foreign_address']:
fadd = entry['foreign_address'].rsplit(':', maxsplit=1)[0]
fport = entry['foreign_address'].rsplit(':', maxsplit=1)[1]
entry['foreign_address'] = fadd
entry['foreign_port'] = fport
if 'proto' in entry and 'kind' in entry:
if entry['kind'] == 'network':
if 'tcp' in entry['proto']:
entry['transport_protocol'] = 'tcp'
elif 'udp' in entry['proto']:
entry['transport_protocol'] = 'udp'
else:
entry['transport_protocol'] = None
if '6' in entry['proto']:
entry['network_protocol'] = 'ipv6'
else:
entry['network_protocol'] = 'ipv4'
return raw_data
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
@@ -527,64 +427,34 @@ def parse(data, raw=False, quiet=False):
List of dictionaries. Raw or processed structured data.
"""
import jc.utils
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
raw_output = []
network = False
socket = False
bluetooth = False
headers = ''
network_list = []
socket_list = []
for line in cleandata:
if jc.utils.has_data(data):
if line.startswith('Active Internet'):
network_list = []
network = True
socket = False
bluetooth = False
continue
# check for FreeBSD/OSX vs Linux
# is this from FreeBSD/OSX?
if cleandata[0] == 'Active Internet connections' \
or cleandata[0] == 'Active Internet connections (including servers)' \
or cleandata[0] == 'Active Multipath Internet connections' \
or cleandata[0] == 'Active LOCAL (UNIX) domain sockets' \
or cleandata[0] == 'Registered kernel control modules' \
or cleandata[0] == 'Active kernel event sockets' \
or cleandata[0] == 'Active kernel control sockets' \
or cleandata[0] == 'Routing tables' \
or cleandata[0].startswith('Name '):
if line.startswith('Active UNIX'):
socket_list = []
network = False
socket = True
bluetooth = False
continue
import jc.parsers.netstat_freebsd_osx
raw_output = jc.parsers.netstat_freebsd_osx.parse(cleandata)
if line.startswith('Active Bluetooth'):
network = False
socket = False
bluetooth = True
continue
if line.startswith('Proto'):
header_text = normalize_headers(line)
headers = header_text.split()
continue
if network:
network_list.append(parse_network(headers, line))
continue
if socket:
socket_list.append(parse_socket(header_text, headers, line))
continue
if bluetooth:
# maybe implement later if requested
continue
for item in [network_list, socket_list]:
for entry in item:
raw_output.append(entry)
raw_output = parse_post(raw_output)
# use linux parser
else:
import jc.parsers.netstat_linux
raw_output = jc.parsers.netstat_linux.parse(cleandata)
if raw:
return raw_output

View File

@@ -0,0 +1,320 @@
"""jc - JSON CLI output utility FreeBSD and OSX netstat Parser"""
def normalize_headers(header):
header = header.lower()
header = header.replace('local address', 'local_address')
header = header.replace('foreign address', 'foreign_address')
header = header.replace('(state)', 'state')
header = header.replace('inode', 'unix_inode')
header = header.replace('flags', 'unix_flags')
header = header.replace('-', '_')
return header
def normalize_route_headers(header):
header = header.lower()
header = header.replace('flags', 'route_flags')
header = header.replace('refs', 'route_refs')
header = header.replace('netif', 'iface')
header = header.replace('-', '_')
return header
def normalize_interface_headers(header):
header = header.lower()
header = header.replace('name', 'iface')
header = header.replace('-', '_')
return header
def parse_item(headers, entry, kind):
entry = entry.split(maxsplit=len(headers) - 1)
# fixup udp records with no state field entry
if kind == 'network' and entry[0].startswith('udp'):
entry.insert(5, None)
if kind == 'network' and 'socket' in headers and 'udp' in str(entry):
entry.insert(7, None)
# fixup -T output on FreeBSD
if kind == 'network' and '0_win' in headers and entry[0].startswith('udp'):
entry.insert(1, '')
entry.insert(1, '')
entry.insert(1, '')
# fixup interface records with no address field entry
if kind == 'interface' and len(entry) == 8:
entry.insert(3, None)
output_line = dict(zip(headers, entry))
output_line['kind'] = kind
return output_line
def parse_post(raw_data):
for entry in raw_data:
# fixup name field in Registered kernel control module
if 'name' in entry:
if entry['name']:
entry['name'] = entry['name'].strip()
# create network and transport protocol fields
if 'local_address' in entry:
if entry['local_address']:
ladd = entry['local_address'].rsplit('.', maxsplit=1)[0]
lport = entry['local_address'].rsplit('.', maxsplit=1)[1]
entry['local_address'] = ladd
entry['local_port'] = lport
if 'foreign_address' in entry:
if entry['foreign_address']:
fadd = entry['foreign_address'].rsplit('.', maxsplit=1)[0]
fport = entry['foreign_address'].rsplit('.', maxsplit=1)[1]
entry['foreign_address'] = fadd
entry['foreign_port'] = fport
if 'proto' in entry and 'kind' in entry:
if entry['kind'] == 'network':
if entry['proto'] == 'udp46':
entry['transport_protocol'] = entry['proto'][:-2]
elif entry['proto'].startswith('icm'):
entry['transport_protocol'] = 'icmp'
else:
entry['transport_protocol'] = entry['proto'][:-1]
if '6' in entry['proto']:
entry['network_protocol'] = 'ipv6'
else:
entry['network_protocol'] = 'ipv4'
# add route_flags_pretty field
if 'route_flags' in entry:
flag_map = {
'1': 'PROTO1',
'2': 'PROTO2',
'3': 'PROTO3',
'B': 'BLACKHOLE',
'b': 'BROADCAST',
'C': 'CLONING',
'c': 'PRCLONING',
'D': 'DYNAMIC',
'G': 'GATEWAY',
'H': 'HOST',
'I': 'IFSCOPE',
'i': 'IFREF',
'L': 'LLINFO',
'M': 'MODIFIED',
'm': 'MULTICAST',
'R': 'REJECT',
'r': 'ROUTER',
'S': 'STATIC',
'U': 'UP',
'W': 'WASCLONED',
'X': 'XRESOLVE',
'Y': 'PROXY',
}
pretty_flags = []
for flag in entry['route_flags']:
if flag in flag_map:
pretty_flags.append(flag_map[flag])
entry['route_flags_pretty'] = pretty_flags
# strip whitespace from beginning and end of all string values
for item in entry:
if isinstance(entry[item], str):
entry[item] = entry[item].strip()
return raw_data
def parse(cleandata):
"""
Main text parsing function for OSX netstat
Parameters:
cleandata: (string) text data to parse
Returns:
List of dictionaries. Raw structured data.
"""
raw_output = []
network = False
multipath = False
socket = False
reg_kernel_control = False
active_kernel_event = False
active_kernel_control = False
routing_table = False
interface_table = False
for line in cleandata:
if line.startswith('Active Internet'):
network = True
multipath = False
socket = False
reg_kernel_control = False
active_kernel_event = False
active_kernel_control = False
routing_table = False
interface_table = False
continue
if line.startswith('Active Multipath Internet connections'):
network = False
multipath = True
socket = False
reg_kernel_control = False
active_kernel_event = False
active_kernel_control = False
routing_table = False
interface_table = False
continue
if line.startswith('Active LOCAL (UNIX) domain sockets') or line.startswith('Active UNIX domain sockets'):
network = False
multipath = False
socket = True
reg_kernel_control = False
active_kernel_event = False
active_kernel_control = False
routing_table = False
interface_table = False
continue
if line.startswith('Registered kernel control modules'):
network = False
multipath = False
socket = False
reg_kernel_control = True
active_kernel_event = False
active_kernel_control = False
routing_table = False
interface_table = False
continue
if line.startswith('Active kernel event sockets'):
network = False
multipath = False
socket = False
reg_kernel_control = False
active_kernel_event = True
active_kernel_control = False
routing_table = False
interface_table = False
continue
if line.startswith('Active kernel control sockets'):
network = False
multipath = False
socket = False
reg_kernel_control = False
active_kernel_event = False
active_kernel_control = True
routing_table = False
interface_table = False
continue
if line.startswith('Routing tables'):
network = False
multipath = False
socket = False
reg_kernel_control = False
active_kernel_event = False
active_kernel_control = False
routing_table = True
interface_table = False
continue
if line.startswith('Name '):
network = False
multipath = False
socket = False
reg_kernel_control = False
active_kernel_event = False
active_kernel_control = False
routing_table = False
interface_table = True
# don't continue since there is no real header row for this table
# get headers
if network and (line.startswith('Socket ') or line.startswith('Proto ') or line.startswith('Tcpcb ')):
header_text = normalize_headers(line)
headers = header_text.split()
continue
if socket and line.startswith('Address '):
header_text = normalize_headers(line)
headers = header_text.split()
continue
if reg_kernel_control and (line.startswith('id ') or line.startswith('kctlref ')):
header_text = normalize_headers(line)
headers = header_text.split()
continue
if active_kernel_event and (line.startswith('Proto ') or line.startswith(' pcb ')):
header_text = normalize_headers(line)
headers = header_text.split()
continue
if active_kernel_control and (line.startswith('Proto ') or line.startswith(' pcb ')):
header_text = normalize_headers(line)
headers = header_text.split()
continue
if routing_table and line.startswith('Destination '):
header_text = normalize_route_headers(line)
headers = header_text.split()
continue
if interface_table and line.startswith('Name '):
header_text = normalize_interface_headers(line)
headers = header_text.split()
continue
# get items
if network:
raw_output.append(parse_item(headers, line, 'network'))
continue
if multipath:
# not implemented
continue
if socket:
raw_output.append(parse_item(headers, line, 'socket'))
continue
if reg_kernel_control:
raw_output.append(parse_item(headers, line, 'Registered kernel control module'))
continue
if active_kernel_event:
raw_output.append(parse_item(headers, line, 'Active kernel event socket'))
continue
if active_kernel_control:
raw_output.append(parse_item(headers, line, 'Active kernel control socket'))
continue
if routing_table and not (line.startswith('Internet:') or line.startswith('Internet6:')):
raw_output.append(parse_item(headers, line, 'route'))
continue
if interface_table:
raw_output.append(parse_item(headers, line, 'interface'))
continue
return parse_post(raw_output)

280
jc/parsers/netstat_linux.py Normal file
View File

@@ -0,0 +1,280 @@
"""jc - JSON CLI output utility Linux netstat Parser"""
import string
def normalize_headers(header):
header = header.lower()
header = header.replace('local address', 'local_address')
header = header.replace('foreign address', 'foreign_address')
header = header.replace('pid/program name', 'program_name')
header = header.replace('security context', 'security_context')
header = header.replace('i-node', 'inode')
header = header.replace('-', '_')
return header
def normalize_route_headers(header):
header = header.lower()
header = header.replace('flags', 'route_flags')
header = header.replace('ref', 'route_refs')
header = header.replace('-', '_')
return header
def normalize_interface_headers(header):
header = header.lower()
header = header.replace('-', '_')
return header
def parse_network(headers, entry):
# Count words in header
# if len of line is one less than len of header, then insert None in field 5
entry = entry.split(maxsplit=len(headers) - 1)
if len(entry) == len(headers) - 1:
entry.insert(5, None)
output_line = dict(zip(headers, entry))
output_line['kind'] = 'network'
return output_line
def parse_socket(header_text, headers, entry):
# get the column # of first letter of "state"
state_col = header_text.find('state')
# get the program name column area
pn_start = header_text.find('program_name')
pn_end = header_text.find('path') - 1
# remove [ and ] from each line
entry = entry.replace('[ ]', '---')
entry = entry.replace('[', ' ').replace(']', ' ')
# find program_name column area and substitute spaces with \u2063 there
old_pn = entry[pn_start:pn_end]
new_pn = old_pn.replace(' ', '\u2063')
entry = entry.replace(old_pn, new_pn)
entry_list = entry.split(maxsplit=len(headers) - 1)
# check column # to see if state column is populated
if entry[state_col] in string.whitespace:
entry_list.insert(4, None)
output_line = dict(zip(headers, entry_list))
output_line['kind'] = 'socket'
# fix program_name field to turn \u2063 back to spaces
if 'program_name' in output_line:
if output_line['program_name']:
old_d_pn = output_line['program_name']
new_d_pn = old_d_pn.replace('\u2063', ' ')
output_line['program_name'] = new_d_pn
return output_line
def parse_route(headers, entry):
entry = entry.split(maxsplit=len(headers) - 1)
output_line = dict(zip(headers, entry))
output_line['kind'] = 'route'
return output_line
def parse_interface(headers, entry):
entry = entry.split(maxsplit=len(headers) - 1)
output_line = dict(zip(headers, entry))
output_line['kind'] = 'interface'
return output_line
def parse_post(raw_data):
# clean up trailing whitespace on each item in each entry
# flags --- = null
# program_name - = null
# split pid and program name and ip addresses and ports
# create network and transport protocol fields
for entry in raw_data:
for item in entry:
try:
entry[item] = entry[item].rstrip()
except (AttributeError):
# skips trying to rstrip Null entries
pass
if 'flags' in entry:
if entry['flags'] == '---':
entry['flags'] = None
if 'program_name' in entry:
entry['program_name'] = entry['program_name'].strip()
if entry['program_name'] == '-':
entry['program_name'] = None
if entry['program_name']:
pid = entry['program_name'].split('/', maxsplit=1)[0]
name = entry['program_name'].split('/', maxsplit=1)[1]
entry['pid'] = pid
entry['program_name'] = name
if 'local_address' in entry:
if entry['local_address']:
ladd = entry['local_address'].rsplit(':', maxsplit=1)[0]
lport = entry['local_address'].rsplit(':', maxsplit=1)[1]
entry['local_address'] = ladd
entry['local_port'] = lport
if 'foreign_address' in entry:
if entry['foreign_address']:
fadd = entry['foreign_address'].rsplit(':', maxsplit=1)[0]
fport = entry['foreign_address'].rsplit(':', maxsplit=1)[1]
entry['foreign_address'] = fadd
entry['foreign_port'] = fport
if 'proto' in entry and 'kind' in entry:
if entry['kind'] == 'network':
if 'tcp' in entry['proto']:
entry['transport_protocol'] = 'tcp'
elif 'udp' in entry['proto']:
entry['transport_protocol'] = 'udp'
else:
entry['transport_protocol'] = None
if '6' in entry['proto']:
entry['network_protocol'] = 'ipv6'
else:
entry['network_protocol'] = 'ipv4'
# add route_flags_pretty
# Flag mapping from https://www.man7.org/linux/man-pages/man8/route.8.html
if 'route_flags' in entry:
flag_map = {
'U': 'UP',
'H': 'HOST',
'G': 'GATEWAY',
'R': 'REINSTATE',
'D': 'DYNAMIC',
'M': 'MODIFIED',
'A': 'ADDRCONF',
'C': 'CACHE',
'!': 'REJECT'
}
pretty_flags = []
for flag in entry['route_flags']:
if flag in flag_map:
pretty_flags.append(flag_map[flag])
entry['route_flags_pretty'] = pretty_flags
return raw_data
def parse(cleandata):
"""
Main text parsing function for OSX netstat
Parameters:
cleandata: (string) text data to parse
Returns:
List of dictionaries. Raw structured data.
"""
raw_output = []
network = False
socket = False
bluetooth = False
routing_table = False
interface_table = False
headers = None
for line in cleandata:
if line.startswith('Active Internet'):
network = True
socket = False
bluetooth = False
routing_table = False
interface_table = False
continue
if line.startswith('Active UNIX'):
network = False
socket = True
bluetooth = False
routing_table = False
interface_table = False
continue
if line.startswith('Active Bluetooth'):
network = False
socket = False
bluetooth = True
routing_table = False
interface_table = False
continue
if line.startswith('Kernel IP routing table'):
network = False
socket = False
bluetooth = False
routing_table = True
interface_table = False
continue
if line.startswith('Kernel Interface table'):
network = False
socket = False
bluetooth = False
routing_table = False
interface_table = True
continue
# get headers
if line.startswith('Proto'):
header_text = normalize_headers(line)
headers = header_text.split()
continue
if line.startswith('Destination '):
header_text = normalize_route_headers(line)
headers = header_text.split()
continue
if line.startswith('Iface '):
header_text = normalize_interface_headers(line)
headers = header_text.split()
continue
# parse items
if network:
raw_output.append(parse_network(headers, line))
continue
if socket:
raw_output.append(parse_socket(header_text, headers, line))
continue
if bluetooth:
# not implemented
continue
if routing_table:
raw_output.append(parse_route(headers, line))
continue
if interface_table:
raw_output.append(parse_interface(headers, line))
continue
return parse_post(raw_output)

View File

@@ -6,7 +6,7 @@ Usage:
Compatibility:
'linux'
'linux', 'freebsd'
Examples:
@@ -183,13 +183,13 @@ import jc.parsers.universal
class info():
version = '1.0'
version = '1.3'
description = 'ntpq -p command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux']
compatible = ['linux', 'freebsd']
magic_commands = ['ntpq']
@@ -268,28 +268,30 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()
raw_output = []
cleandata = data.splitlines()
cleandata[0] = 's ' + cleandata[0]
cleandata[0] = cleandata[0].lower()
if jc.utils.has_data(data):
# delete header delimiter
del cleandata[1]
cleandata[0] = 's ' + cleandata[0]
cleandata[0] = cleandata[0].lower()
# separate first character with a space for easier parsing
for i, line in list(enumerate(cleandata[1:])):
if line[0] == ' ':
# fixup for no-state
cleandata[i + 1] = '~ ' + line[1:]
else:
# fixup - realign columns since we added the 's' column
cleandata[i + 1] = line[:1] + ' ' + line[1:]
# delete header delimiter
del cleandata[1]
# fixup for occaisional ip/hostname fields with a space
cleandata[i + 1] = cleandata[i + 1].replace(' (', '_(')
# separate first character with a space for easier parsing
for i, line in list(enumerate(cleandata[1:])):
if line[0] == ' ':
# fixup for no-state
cleandata[i + 1] = '~ ' + line[1:]
else:
# fixup - realign columns since we added the 's' column
cleandata[i + 1] = line[:1] + ' ' + line[1:]
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
# fixup for occaisional ip/hostname fields with a space
cleandata[i + 1] = cleandata[i + 1].replace(' (', '_(')
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if raw:
return raw_output

View File

@@ -78,7 +78,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = '/etc/passwd file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -146,12 +146,12 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = []
cleandata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
for entry in cleandata:
if entry.startswith('#'):
continue

View File

@@ -32,7 +32,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.3'
description = 'pip list command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -88,28 +88,28 @@ def parse(data, raw=False, quiet=False):
raw_output = []
linedata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, linedata))
cleandata = list(filter(None, data.splitlines()))
# detect legacy output type
if ' (' in cleandata[0]:
for row in cleandata:
raw_output.append({'package': row.split(' (')[0],
'version': row.split(' (')[1].rstrip(')')})
if jc.utils.has_data(data):
# otherwise normal table output
else:
# clear separator line
for i, line in reversed(list(enumerate(cleandata))):
if '---' in line:
cleandata.pop(i)
# detect legacy output type
if ' (' in cleandata[0]:
for row in cleandata:
raw_output.append({'package': row.split(' (')[0],
'version': row.split(' (')[1].rstrip(')')})
cleandata[0] = cleandata[0].lower()
# otherwise normal table output
else:
# clear separator line
for i, line in reversed(list(enumerate(cleandata))):
if '---' in line:
cleandata.pop(i)
if cleandata:
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
cleandata[0] = cleandata[0].lower()
if cleandata:
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if raw:
return raw_output

View File

@@ -42,7 +42,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'pip show command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -107,12 +107,11 @@ def parse(data, raw=False, quiet=False):
raw_output = []
package = {}
linedata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, linedata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
for row in cleandata:
if row.startswith('---'):
raw_output.append(package)

View File

@@ -177,7 +177,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.3'
description = 'ps command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -282,9 +282,12 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()
cleandata[0] = cleandata[0].lower()
raw_output = []
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if jc.utils.has_data(data):
cleandata[0] = cleandata[0].lower()
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if raw:
return raw_output

View File

@@ -14,53 +14,48 @@ Examples:
[
{
"destination": "default",
"gateway": "gateway",
"gateway": "_gateway",
"genmask": "0.0.0.0",
"flags": "UG",
"metric": 100,
"metric": 202,
"ref": 0,
"use": 0,
"iface": "ens33",
"mss": 0,
"window": 0,
"irtt": 0
},
{
"destination": "172.17.0.0",
"gateway": "0.0.0.0",
"genmask": "255.255.0.0",
"flags": "U",
"metric": 0,
"ref": 0,
"use": 0,
"iface": "docker",
"mss": 0,
"window": 0,
"irtt": 0
"irtt": 0,
"flags_pretty": [
"UP",
"GATEWAY"
]
},
{
"destination": "192.168.71.0",
"gateway": "0.0.0.0",
"genmask": "255.255.255.0",
"flags": "U",
"metric": 100,
"metric": 202,
"ref": 0,
"use": 0,
"iface": "ens33",
"mss": 0,
"window": 0,
"irtt": 0
"irtt": 0,
"flags_pretty": [
"UP"
]
}
]
$ route -ee | jc --route -p -r
[
{
"destination": "default",
"gateway": "gateway",
"gateway": "_gateway",
"genmask": "0.0.0.0",
"flags": "UG",
"metric": "100",
"metric": "202",
"ref": "0",
"use": "0",
"iface": "ens33",
@@ -68,25 +63,12 @@ Examples:
"window": "0",
"irtt": "0"
},
{
"destination": "172.17.0.0",
"gateway": "0.0.0.0",
"genmask": "255.255.0.0",
"flags": "U",
"metric": "0",
"ref": "0",
"use": "0",
"iface": "docker",
"mss": "0",
"window": "0",
"irtt": "0"
},
{
"destination": "192.168.71.0",
"gateway": "0.0.0.0",
"genmask": "255.255.255.0",
"flags": "U",
"metric": "100",
"metric": "202",
"ref": "0",
"use": "0",
"iface": "ens33",
@@ -95,13 +77,14 @@ Examples:
"irtt": "0"
}
]
"""
import jc.utils
import jc.parsers.universal
class info():
version = '1.0'
version = '1.3'
description = 'route command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -128,17 +111,20 @@ def process(proc_data):
[
{
"destination": string,
"gateway": string,
"genmask": string,
"flags": string,
"metric": integer,
"ref": integer,
"use": integer,
"mss": integer,
"window": integer,
"irtt": integer,
"iface": string
"destination": string,
"gateway": string,
"genmask": string,
"flags": string,
"flags_pretty": [
string,
]
"metric": integer,
"ref": integer,
"use": integer,
"mss": integer,
"window": integer,
"irtt": integer,
"iface": string
}
]
"""
@@ -152,6 +138,29 @@ def process(proc_data):
except (ValueError):
entry[key] = None
# add flags_pretty
# Flag mapping from https://www.man7.org/linux/man-pages/man8/route.8.html
if 'flags' in entry:
flag_map = {
'U': 'UP',
'H': 'HOST',
'G': 'GATEWAY',
'R': 'REINSTATE',
'D': 'DYNAMIC',
'M': 'MODIFIED',
'A': 'ADDRCONF',
'C': 'CACHE',
'!': 'REJECT'
}
pretty_flags = []
for flag in entry['flags']:
if flag in flag_map:
pretty_flags.append(flag_map[flag])
entry['flags_pretty'] = pretty_flags
return proc_data
@@ -173,9 +182,12 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()[1:]
cleandata[0] = cleandata[0].lower()
raw_output = []
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if jc.utils.has_data(data):
cleandata[0] = cleandata[0].lower()
raw_output = jc.parsers.universal.simple_table_parse(cleandata)
if raw:
return raw_output

View File

@@ -84,7 +84,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = '/etc/shadow file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -153,12 +153,12 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = []
cleandata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
for entry in cleandata:
if entry.startswith('#'):
continue

View File

@@ -251,7 +251,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.2'
description = 'ss command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -308,17 +308,17 @@ def process(proc_data):
except (ValueError):
entry[key] = None
if 'local_port' in entry:
if 'local_port' in entry:
try:
entry['local_port_num'] = int(entry['local_port'])
except (ValueError):
pass
if 'peer_port' in entry:
try:
entry['peer_port_num'] = int(entry['peer_port'])
except (ValueError):
pass
if 'peer_port' in entry:
try:
entry['peer_port_num'] = int(entry['peer_port'])
except (ValueError):
pass
return proc_data
@@ -342,12 +342,12 @@ def parse(data, raw=False, quiet=False):
contains_colon = ['nl', 'p_raw', 'raw', 'udp', 'tcp', 'v_str', 'icmp6']
raw_output = []
cleandata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
header_text = cleandata[0].lower()
header_text = header_text.replace('netidstate', 'netid state')
header_text = header_text.replace('local address:port', 'local_address local_port')

View File

@@ -6,7 +6,7 @@ Usage:
Compatibility:
'linux'
'linux', 'darwin', 'freebsd'
Examples:
@@ -100,17 +100,18 @@ Examples:
..
]
"""
import shlex
import jc.utils
class info():
version = '1.1'
version = '1.5'
description = 'stat command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux']
compatible = ['linux', 'darwin', 'freebsd']
magic_commands = ['stat']
@@ -149,12 +150,16 @@ def process(proc_data):
"access_time": string, # - = null
"modify_time": string, # - = null
"change_time": string, # - = null
"birth_time": string # - = null
"birth_time": string, # - = null
"unix_device": integer,
"rdev": integer,
"block_size": integer,
"unix_flags": string
}
]
"""
for entry in proc_data:
int_list = ['size', 'blocks', 'io_blocks', 'inode', 'links', 'uid', 'gid']
int_list = ['size', 'blocks', 'io_blocks', 'inode', 'links', 'uid', 'gid', 'unix_device', 'rdev', 'block_size']
for key in int_list:
if key in entry:
try:
@@ -192,87 +197,114 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = []
cleandata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
if cleandata:
# stats output contains 8 lines
for line in cleandata:
if jc.utils.has_data(data):
# line #1
if line.find('File:') == 2:
output_line = {}
line_list = line.split(maxsplit=1)
output_line['file'] = line_list[1]
# linux output
if cleandata[0].startswith(' File: '):
# stats output contains 8 lines
for line in cleandata:
# 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
# line #1
if line.find('File:') == 2:
output_line = {}
line_list = line.split(maxsplit=1)
output_line['file'] = line_list[1]
continue
# 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
# 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
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 #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 #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 #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 #5
if line.startswith('Access: 2'):
line_list = line.split(maxsplit=1)
output_line['access_time'] = line_list[1]
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 #6
if line.startswith('Modify:'):
line_list = line.split(maxsplit=1)
output_line['modify_time'] = line_list[1]
continue
# line #5
if line.startswith('Access: 2'):
line_list = line.split(maxsplit=1)
output_line['access_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 #6
if line.startswith('Modify:'):
line_list = line.split(maxsplit=1)
output_line['modify_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]
# 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': 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)
continue
if raw:
return raw_output

View File

@@ -40,7 +40,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.3'
description = 'systemctl command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -96,27 +96,30 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
linedata = data.splitlines()
# Clear any blank lines
linedata = list(filter(None, linedata))
# clean up non-ascii characters, if any
cleandata = []
for entry in linedata:
cleandata.append(entry.encode('ascii', errors='ignore').decode())
header_text = cleandata[0]
header_list = header_text.lower().split()
linedata = list(filter(None, data.splitlines()))
raw_output = []
for entry in cleandata[1:]:
if 'LOAD = ' in entry:
break
if jc.utils.has_data(data):
else:
entry_list = entry.rstrip().split(maxsplit=4)
output_line = dict(zip(header_list, entry_list))
raw_output.append(output_line)
# clean up non-ascii characters, if any
cleandata = []
for entry in linedata:
cleandata.append(entry.encode('ascii', errors='ignore').decode())
header_text = cleandata[0]
header_list = header_text.lower().split()
raw_output = []
for entry in cleandata[1:]:
if 'LOAD = ' in entry:
break
else:
entry_list = entry.rstrip().split(maxsplit=4)
output_line = dict(zip(header_list, entry_list))
raw_output.append(output_line)
if raw:
return raw_output

View File

@@ -59,7 +59,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.3'
description = 'systemctl list-jobs command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -122,28 +122,32 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
linedata = data.splitlines()
# Clear any blank lines
linedata = list(filter(None, linedata))
# clean up non-ascii characters, if any
cleandata = []
for entry in linedata:
cleandata.append(entry.encode('ascii', errors='ignore').decode())
header_text = cleandata[0]
header_text = header_text.lower()
header_list = header_text.split()
linedata = list(filter(None, data.splitlines()))
raw_output = []
for entry in cleandata[1:]:
if 'No jobs running.' in entry or 'jobs listed.' in entry:
break
if jc.utils.has_data(data):
else:
entry_list = entry.split(maxsplit=4)
output_line = dict(zip(header_list, entry_list))
raw_output.append(output_line)
cleandata = []
# clean up non-ascii characters, if any
for entry in linedata:
cleandata.append(entry.encode('ascii', errors='ignore').decode())
header_text = cleandata[0]
header_text = header_text.lower()
header_list = header_text.split()
raw_output = []
for entry in cleandata[1:]:
if 'No jobs running.' in entry or 'jobs listed.' in entry:
break
else:
entry_list = entry.split(maxsplit=4)
output_line = dict(zip(header_list, entry_list))
raw_output.append(output_line)
if raw:
return raw_output

View File

@@ -34,7 +34,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.3'
description = 'systemctl list-sockets command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -88,27 +88,30 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
linedata = data.splitlines()
# Clear any blank lines
linedata = list(filter(None, linedata))
# clean up non-ascii characters, if any
cleandata = []
for entry in linedata:
cleandata.append(entry.encode('ascii', errors='ignore').decode())
header_text = cleandata[0].lower()
header_list = header_text.split()
linedata = list(filter(None, data.splitlines()))
raw_output = []
for entry in cleandata[1:]:
if 'sockets listed.' in entry:
break
if jc.utils.has_data(data):
else:
entry_list = entry.rsplit(maxsplit=2)
output_line = dict(zip(header_list, entry_list))
raw_output.append(output_line)
cleandata = []
# clean up non-ascii characters, if any
for entry in linedata:
cleandata.append(entry.encode('ascii', errors='ignore').decode())
header_text = cleandata[0].lower()
header_list = header_text.split()
raw_output = []
for entry in cleandata[1:]:
if 'sockets listed.' in entry:
break
else:
entry_list = entry.rsplit(maxsplit=2)
output_line = dict(zip(header_list, entry_list))
raw_output.append(output_line)
if raw:
return raw_output

View File

@@ -31,7 +31,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.3'
description = 'systemctl list-unit-files command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -84,28 +84,31 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
linedata = data.splitlines()
# Clear any blank lines
linedata = list(filter(None, linedata))
# clean up non-ascii characters, if any
cleandata = []
for entry in linedata:
cleandata.append(entry.encode('ascii', errors='ignore').decode())
header_text = cleandata[0]
header_text = header_text.lower().replace('unit file', 'unit_file')
header_list = header_text.split()
linedata = list(filter(None, data.splitlines()))
raw_output = []
for entry in cleandata[1:]:
if 'unit files listed.' in entry:
break
if jc.utils.has_data(data):
else:
entry_list = entry.split(maxsplit=4)
output_line = dict(zip(header_list, entry_list))
raw_output.append(output_line)
cleandata = []
# clean up non-ascii characters, if any
for entry in linedata:
cleandata.append(entry.encode('ascii', errors='ignore').decode())
header_text = cleandata[0]
header_text = header_text.lower().replace('unit file', 'unit_file')
header_list = header_text.split()
raw_output = []
for entry in cleandata[1:]:
if 'unit files listed.' in entry:
break
else:
entry_list = entry.split(maxsplit=4)
output_line = dict(zip(header_list, entry_list))
raw_output.append(output_line)
if raw:
return raw_output

View File

@@ -38,7 +38,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'timedatectl status command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -109,12 +109,14 @@ def parse(data, raw=False, quiet=False):
raw_output = {}
for line in filter(None, data.splitlines()):
linedata = line.split(':', maxsplit=1)
raw_output[linedata[0].strip().lower().replace(' ', '_')] = linedata[1].strip()
if jc.utils.has_data(data):
if linedata[0].strip() == 'DST active':
break
for line in filter(None, data.splitlines()):
linedata = line.split(':', maxsplit=1)
raw_output[linedata[0].strip().lower().replace(' ', '_')] = linedata[1].strip()
if linedata[0].strip() == 'DST active':
break
if raw:
return raw_output

View File

@@ -10,7 +10,7 @@ Limitations:
Compatibility:
'linux', 'darwin'
'linux', 'darwin', 'freebsd'
Example:
@@ -30,13 +30,13 @@ import jc.utils
class info():
version = '1.1'
version = '1.3'
description = 'uname -a command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux', 'darwin']
compatible = ['linux', 'darwin', 'freebsd']
magic_commands = ['uname']
@@ -88,9 +88,9 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = {}
split_line = data.split()
if len(split_line) > 1:
if jc.utils.has_data(data):
# check for OSX output
if data.startswith('Darwin'):
parsed_line = data.split()

View File

@@ -34,7 +34,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.2'
description = 'uptime command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -107,10 +107,10 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = {}
cleandata = data.splitlines()
if cleandata:
if jc.utils.has_data(data):
parsed_line = cleandata[0].split()
# allow space for odd times

View File

@@ -83,7 +83,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.3'
description = 'w command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -149,30 +149,40 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
cleandata = data.splitlines()[1:]
header_text = cleandata[0].lower()
# fixup for 'from' column that can be blank
from_col = header_text.find('from')
# clean up 'login@' header
# even though @ in a key is valid json, it can make things difficult
header_text = header_text.replace('login@', 'login_at')
headers = [h for h in ' '.join(header_text.strip().split()).split() if h]
# parse lines
raw_output = []
if cleandata:
for entry in cleandata[1:]:
output_line = {}
# normalize data by inserting Null for missing data
temp_line = entry.split(maxsplit=len(headers) - 1)
if jc.utils.has_data(data):
# fix from column, always at column 2
if 'from' in headers:
if entry[from_col] in string.whitespace:
temp_line.insert(2, '-')
header_text = cleandata[0].lower()
# fixup for 'from' column that can be blank
from_col = header_text.find('from')
# clean up 'login@' header
# even though @ in a key is valid json, it can make things difficult
header_text = header_text.replace('login@', 'login_at')
headers = [h for h in ' '.join(header_text.strip().split()).split() if h]
output_line = dict(zip(headers, temp_line))
raw_output.append(output_line)
# parse lines
raw_output = []
if cleandata:
for entry in cleandata[1:]:
output_line = {}
# normalize data by inserting Null for missing data
temp_line = entry.split(maxsplit=len(headers) - 1)
# fix from column, always at column 2
if 'from' in headers:
if entry[from_col] in string.whitespace:
temp_line.insert(2, '-')
output_line = dict(zip(headers, temp_line))
raw_output.append(output_line)
# strip whitespace from beginning and end of all string values
for row in raw_output:
for item in row:
if isinstance(row[item], str):
row[item] = row[item].strip()
if raw:
return raw_output

View File

@@ -103,7 +103,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'who command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -174,12 +174,12 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = []
cleandata = data.splitlines()
# Clear any blank lines
cleandata = list(filter(None, cleandata))
cleandata = list(filter(None, data.splitlines()))
if jc.utils.has_data(data):
if cleandata:
for line in cleandata:
output_line = {}
linedata = line.split()

View File

@@ -59,7 +59,7 @@ import xmltodict
class info():
version = '1.0'
version = '1.2'
description = 'XML file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -111,7 +111,10 @@ def parse(data, raw=False, quiet=False):
if not quiet:
jc.utils.compatibility(__name__, info.compatible)
if data:
raw_output = []
if jc.utils.has_data(data):
raw_output = xmltodict.parse(data)
if raw:

View File

@@ -71,7 +71,7 @@ from ruamel.yaml import YAML
class info():
version = '1.0'
version = '1.1'
description = 'YAML file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -126,10 +126,13 @@ def parse(data, raw=False, quiet=False):
jc.utils.compatibility(__name__, info.compatible)
raw_output = []
yaml = YAML(typ='safe')
for document in yaml.load_all(data):
raw_output.append(document)
if jc.utils.has_data(data):
yaml = YAML(typ='safe')
for document in yaml.load_all(data):
raw_output.append(document)
if raw:
return raw_output

View File

@@ -56,7 +56,29 @@ def compatibility(mod_name, compatible):
no return, just prints output to STDERR
"""
if sys.platform not in compatible:
platform_found = False
for platform in compatible:
if sys.platform.startswith(platform):
platform_found = True
break
if not platform_found:
mod = mod_name.split('.')[-1]
compat_list = ', '.join(compatible)
warning_message(f'{mod} parser not compatible with your OS ({sys.platform}).\n Compatible platforms: {compat_list}')
def has_data(data):
"""
Checks if the input contains data. If there are any non-whitespace characters then return True, else return False
Parameters:
data: (string) input to check whether it contains data
Returns:
Boolean True if input string (data) contains non-whitespace characters, otherwise False
"""
return True if data and not data.isspace() else False

View File

@@ -5,10 +5,10 @@ with open('README.md', 'r') as f:
setuptools.setup(
name='jc',
version='1.10.12',
version='1.11.8',
author='Kelly Brazil',
author_email='kellyjonbrazil@gmail.com',
description='This tool serializes the output of popular command line tools and filetypes to structured JSON output.',
description='Converts the output of popular command-line tools and file-types to JSON.',
install_requires=[
'ruamel.yaml>=0.15.0',
'xmltodict>=0.12.0',

File diff suppressed because one or more lines are too long

11810
tests/fixtures/centos-7.7/dmidecode.out vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
[{"chain": "PREROUTING", "rules": [{"target": "PREROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES_SOURCE", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "INPUT", "rules": [{"target": "INPUT_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "FORWARD", "rules": [{"target": "FORWARD_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "OUTPUT", "rules": [{"target": "OUTPUT_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "POSTROUTING", "rules": [{"target": "POSTROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "FORWARD_direct", "rules": []}, {"chain": "INPUT_direct", "rules": []}, {"chain": "OUTPUT_direct", "rules": []}, {"chain": "POSTROUTING_direct", "rules": []}, {"chain": "PREROUTING_ZONES", "rules": [{"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}, {"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}]}, {"chain": "PREROUTING_ZONES_SOURCE", "rules": []}, {"chain": "PREROUTING_direct", "rules": []}, {"chain": "PRE_public", "rules": [{"target": "PRE_public_log", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_deny", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_allow", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "PRE_public_allow", "rules": []}, {"chain": "PRE_public_deny", "rules": []}]
[{"chain": "PREROUTING", "rules": [{"target": "PREROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES_SOURCE", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "INPUT", "rules": [{"target": "INPUT_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "FORWARD", "rules": [{"target": "FORWARD_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "OUTPUT", "rules": [{"target": "OUTPUT_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "POSTROUTING", "rules": [{"target": "POSTROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "FORWARD_direct", "rules": []}, {"chain": "INPUT_direct", "rules": []}, {"chain": "OUTPUT_direct", "rules": []}, {"chain": "POSTROUTING_direct", "rules": []}, {"chain": "PREROUTING_ZONES", "rules": [{"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}, {"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}]}, {"chain": "PREROUTING_ZONES_SOURCE", "rules": []}, {"chain": "PREROUTING_direct", "rules": []}, {"chain": "PRE_public", "rules": [{"target": "PRE_public_log", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_deny", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_allow", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "PRE_public_allow", "rules": []}, {"chain": "PRE_public_deny", "rules": []}, {"chain": "PRE_public_log", "rules": []}]

View File

@@ -1 +1 @@
[{"chain": "PREROUTING", "rules": [{"target": "PREROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES_SOURCE", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "DOCKER", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "ADDRTYPE match dst-type LOCAL"}]}, {"chain": "INPUT", "rules": []}, {"chain": "OUTPUT", "rules": [{"target": "OUTPUT_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "DOCKER", "prot": "all", "opt": null, "source": "anywhere", "destination": "!loopback/8", "options": "ADDRTYPE match dst-type LOCAL"}]}, {"chain": "POSTROUTING", "rules": [{"target": "MASQUERADE", "prot": "all", "opt": null, "source": "172.17.0.0/16", "destination": "anywhere"}, {"target": "POSTROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "POSTROUTING_ZONES_SOURCE", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "POSTROUTING_ZONES", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "DOCKER", "rules": [{"target": "RETURN", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "OUTPUT_direct", "rules": []}, {"chain": "POSTROUTING_ZONES", "rules": [{"target": "POST_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}, {"target": "POST_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}]}, {"chain": "POSTROUTING_ZONES_SOURCE", "rules": []}, {"chain": "POSTROUTING_direct", "rules": []}, {"chain": "POST_public", "rules": [{"target": "POST_public_log", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "POST_public_deny", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "POST_public_allow", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "POST_public_allow", "rules": []}, {"chain": "POST_public_deny", "rules": []}, {"chain": "POST_public_log", "rules": []}, {"chain": "PREROUTING_ZONES", "rules": [{"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}, {"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}]}, {"chain": "PREROUTING_ZONES_SOURCE", "rules": []}, {"chain": "PREROUTING_direct", "rules": []}, {"chain": "PRE_public", "rules": [{"target": "PRE_public_log", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_deny", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_allow", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "PRE_public_allow", "rules": []}, {"chain": "PRE_public_deny", "rules": []}]
[{"chain": "PREROUTING", "rules": [{"target": "PREROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES_SOURCE", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "DOCKER", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "ADDRTYPE match dst-type LOCAL"}]}, {"chain": "INPUT", "rules": []}, {"chain": "OUTPUT", "rules": [{"target": "OUTPUT_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "DOCKER", "prot": "all", "opt": null, "source": "anywhere", "destination": "!loopback/8", "options": "ADDRTYPE match dst-type LOCAL"}]}, {"chain": "POSTROUTING", "rules": [{"target": "MASQUERADE", "prot": "all", "opt": null, "source": "172.17.0.0/16", "destination": "anywhere"}, {"target": "POSTROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "POSTROUTING_ZONES_SOURCE", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "POSTROUTING_ZONES", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "DOCKER", "rules": [{"target": "RETURN", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "OUTPUT_direct", "rules": []}, {"chain": "POSTROUTING_ZONES", "rules": [{"target": "POST_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}, {"target": "POST_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}]}, {"chain": "POSTROUTING_ZONES_SOURCE", "rules": []}, {"chain": "POSTROUTING_direct", "rules": []}, {"chain": "POST_public", "rules": [{"target": "POST_public_log", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "POST_public_deny", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "POST_public_allow", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "POST_public_allow", "rules": []}, {"chain": "POST_public_deny", "rules": []}, {"chain": "POST_public_log", "rules": []}, {"chain": "PREROUTING_ZONES", "rules": [{"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}, {"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}]}, {"chain": "PREROUTING_ZONES_SOURCE", "rules": []}, {"chain": "PREROUTING_direct", "rules": []}, {"chain": "PRE_public", "rules": [{"target": "PRE_public_log", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_deny", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_allow", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "PRE_public_allow", "rules": []}, {"chain": "PRE_public_deny", "rules": []}, {"chain": "PRE_public_log", "rules": []}]

View File

@@ -1 +1 @@
[{"chain": "PREROUTING", "rules": [{"target": "PREROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES_SOURCE", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "OUTPUT", "rules": [{"target": "OUTPUT_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "OUTPUT_direct", "rules": []}, {"chain": "PREROUTING_ZONES", "rules": [{"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}, {"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}]}, {"chain": "PREROUTING_ZONES_SOURCE", "rules": []}, {"chain": "PREROUTING_direct", "rules": []}, {"chain": "PRE_public", "rules": [{"target": "PRE_public_log", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_deny", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_allow", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "PRE_public_allow", "rules": []}, {"chain": "PRE_public_deny", "rules": []}]
[{"chain": "PREROUTING", "rules": [{"target": "PREROUTING_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES_SOURCE", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PREROUTING_ZONES", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "OUTPUT", "rules": [{"target": "OUTPUT_direct", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "OUTPUT_direct", "rules": []}, {"chain": "PREROUTING_ZONES", "rules": [{"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}, {"target": "PRE_public", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere", "options": "[goto] "}]}, {"chain": "PREROUTING_ZONES_SOURCE", "rules": []}, {"chain": "PREROUTING_direct", "rules": []}, {"chain": "PRE_public", "rules": [{"target": "PRE_public_log", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_deny", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}, {"target": "PRE_public_allow", "prot": "all", "opt": null, "source": "anywhere", "destination": "anywhere"}]}, {"chain": "PRE_public_allow", "rules": []}, {"chain": "PRE_public_deny", "rules": []}, {"chain": "PRE_public_log", "rules": []}]

View File

@@ -0,0 +1 @@
[{"iface": "docker0", "mtu": 1500, "rx_ok": 0, "rx_err": 0, "rx_drp": 0, "rx_ovr": 0, "tx_ok": 0, "tx_err": 0, "tx_drp": 0, "tx_ovr": 0, "flg": "BMU", "kind": "interface"}, {"iface": "ens33", "mtu": 1500, "rx_ok": 476, "rx_err": 0, "rx_drp": 0, "rx_ovr": 0, "tx_ok": 312, "tx_err": 0, "tx_drp": 0, "tx_ovr": 0, "flg": "BMRU", "kind": "interface"}, {"iface": "lo", "mtu": 65536, "rx_ok": 0, "rx_err": 0, "rx_drp": 0, "rx_ovr": 0, "tx_ok": 0, "tx_err": 0, "tx_drp": 0, "tx_ovr": 0, "flg": "LRU", "kind": "interface"}]

View File

@@ -0,0 +1,5 @@
Kernel Interface table
Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
docker0 1500 0 0 0 0 0 0 0 0 BMU
ens33 1500 476 0 0 0 312 0 0 0 BMRU
lo 65536 0 0 0 0 0 0 0 0 LRU

View File

@@ -0,0 +1 @@
[{"destination": "default", "gateway": "gateway", "genmask": "0.0.0.0", "route_flags": "UG", "mss": 0, "window": 0, "irtt": 0, "iface": "ens33", "kind": "route", "route_flags_pretty": ["UP", "GATEWAY"]}, {"destination": "172.17.0.0", "gateway": "0.0.0.0", "genmask": "255.255.0.0", "route_flags": "U", "mss": 0, "window": 0, "irtt": 0, "iface": "docker0", "kind": "route", "route_flags_pretty": ["UP"]}, {"destination": "192.168.71.0", "gateway": "0.0.0.0", "genmask": "255.255.255.0", "route_flags": "U", "mss": 0, "window": 0, "irtt": 0, "iface": "ens33", "kind": "route", "route_flags_pretty": ["UP"]}]

View File

@@ -0,0 +1,5 @@
Kernel IP routing table
Destination Gateway Genmask Flags MSS Window irtt Iface
default gateway 0.0.0.0 UG 0 0 0 ens33
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.71.0 0.0.0.0 255.255.255.0 U 0 0 0 ens33

View File

@@ -0,0 +1 @@
[{"destination": "0.0.0.0", "gateway": "192.168.71.2", "genmask": "0.0.0.0", "route_flags": "UG", "metric": 100, "route_refs": 0, "use": 0, "iface": "ens33", "kind": "route", "route_flags_pretty": ["UP", "GATEWAY"]}, {"destination": "172.17.0.0", "gateway": "0.0.0.0", "genmask": "255.255.0.0", "route_flags": "U", "metric": 0, "route_refs": 0, "use": 0, "iface": "docker0", "kind": "route", "route_flags_pretty": ["UP"]}, {"destination": "192.168.71.0", "gateway": "0.0.0.0", "genmask": "255.255.255.0", "route_flags": "U", "metric": 100, "route_refs": 0, "use": 0, "iface": "ens33", "kind": "route", "route_flags_pretty": ["UP"]}]

View File

@@ -0,0 +1,5 @@
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.71.2 0.0.0.0 UG 100 0 0 ens33
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
192.168.71.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33

View File

@@ -0,0 +1 @@
[{"destination": "0.0.0.0", "gateway": "192.168.71.2", "genmask": "0.0.0.0", "route_flags": "UG", "metric": 100, "route_refs": 0, "use": 0, "iface": "ens33", "mss": 0, "window": 0, "irtt": 0, "kind": "route", "route_flags_pretty": ["UP", "GATEWAY"]}, {"destination": "172.17.0.0", "gateway": "0.0.0.0", "genmask": "255.255.0.0", "route_flags": "U", "metric": 0, "route_refs": 0, "use": 0, "iface": "docker", "mss": 0, "window": 0, "irtt": 0, "kind": "route", "route_flags_pretty": ["UP"]}, {"destination": "192.168.71.0", "gateway": "0.0.0.0", "genmask": "255.255.255.0", "route_flags": "U", "metric": 100, "route_refs": 0, "use": 0, "iface": "ens33", "mss": 0, "window": 0, "irtt": 0, "kind": "route", "route_flags_pretty": ["UP"]}]

View File

@@ -0,0 +1,5 @@
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface MSS Window irtt
0.0.0.0 192.168.71.2 0.0.0.0 UG 100 0 0 ens33 0 0 0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker 0 0 0
192.168.71.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33 0 0 0

View File

@@ -1 +1 @@
[{"destination": "0.0.0.0", "gateway": "192.168.71.2", "genmask": "0.0.0.0", "flags": "UG", "metric": 100, "ref": 0, "use": 0, "iface": "ens33"}, {"destination": "172.17.0.0", "gateway": "0.0.0.0", "genmask": "255.255.0.0", "flags": "U", "metric": 0, "ref": 0, "use": 0, "iface": "docker0"}, {"destination": "192.168.71.0", "gateway": "0.0.0.0", "genmask": "255.255.255.0", "flags": "U", "metric": 100, "ref": 0, "use": 0, "iface": "ens33"}]
[{"destination": "0.0.0.0", "gateway": "192.168.71.2", "genmask": "0.0.0.0", "flags": "UG", "metric": 100, "ref": 0, "use": 0, "iface": "ens33", "flags_pretty": ["UP", "GATEWAY"]}, {"destination": "172.17.0.0", "gateway": "0.0.0.0", "genmask": "255.255.0.0", "flags": "U", "metric": 0, "ref": 0, "use": 0, "iface": "docker0", "flags_pretty": ["UP"]}, {"destination": "192.168.71.0", "gateway": "0.0.0.0", "genmask": "255.255.255.0", "flags": "U", "metric": 100, "ref": 0, "use": 0, "iface": "ens33", "flags_pretty": ["UP"]}]

View File

@@ -1 +1 @@
[{"destination": "default", "gateway": "gateway", "genmask": "0.0.0.0", "flags": "UG", "metric": 100, "ref": 0, "use": 0, "iface": "ens33"}, {"destination": "172.17.0.0", "gateway": "0.0.0.0", "genmask": "255.255.0.0", "flags": "U", "metric": 0, "ref": 0, "use": 0, "iface": "docker0"}, {"destination": "192.168.71.0", "gateway": "0.0.0.0", "genmask": "255.255.255.0", "flags": "U", "metric": 100, "ref": 0, "use": 0, "iface": "ens33"}]
[{"destination": "default", "gateway": "gateway", "genmask": "0.0.0.0", "flags": "UG", "metric": 100, "ref": 0, "use": 0, "iface": "ens33", "flags_pretty": ["UP", "GATEWAY"]}, {"destination": "172.17.0.0", "gateway": "0.0.0.0", "genmask": "255.255.0.0", "flags": "U", "metric": 0, "ref": 0, "use": 0, "iface": "docker0", "flags_pretty": ["UP"]}, {"destination": "192.168.71.0", "gateway": "0.0.0.0", "genmask": "255.255.255.0", "flags": "U", "metric": 100, "ref": 0, "use": 0, "iface": "ens33", "flags_pretty": ["UP"]}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

11810
tests/fixtures/fedora32/dmidecode.out vendored Normal file

File diff suppressed because it is too large Load Diff

1
tests/fixtures/freebsd12/arp-a.json vendored Normal file
View File

@@ -0,0 +1 @@
[{"name": null, "address": "192.168.71.163", "hwtype": "ethernet", "hwaddress": "00:0c:29:1a:4e:3b", "iface": "em0", "permanent": true}, {"name": null, "address": "192.168.71.2", "hwtype": "ethernet", "hwaddress": "00:50:56:f7:4a:fc", "iface": "em0", "permanent": false, "expires": 942}, {"name": null, "address": "192.168.71.1", "hwtype": "ethernet", "hwaddress": "00:50:56:c0:00:08", "iface": "em0", "permanent": false, "expires": 1182}]

3
tests/fixtures/freebsd12/arp-a.out vendored Normal file
View File

@@ -0,0 +1,3 @@
? (192.168.71.163) at 00:0c:29:1a:4e:3b on em0 permanent [ethernet]
? (192.168.71.2) at 00:50:56:f7:4a:fc on em0 expires in 942 seconds [ethernet]
? (192.168.71.1) at 00:50:56:c0:00:08 on em0 expires in 1182 seconds [ethernet]

1
tests/fixtures/freebsd12/last.json vendored Normal file
View File

@@ -0,0 +1 @@
[{"user": "kbrazil", "tty": "pts/0", "hostname": "192.168.71.1", "login": "Thu May 28 21:29", "logout": "still logged in"}, {"user": "kbrazil", "tty": "pts/0", "hostname": "192.168.71.1", "login": "Thu May 28 04:13", "logout": "21:29", "duration": "17:16"}, {"user": "kbrazil", "tty": "pts/0", "hostname": "192.168.71.1", "login": "Wed May 27 16:09", "logout": "04:13", "duration": "12:03"}, {"user": "root", "tty": "ttyv0", "hostname": null, "login": "Wed May 27 12:48", "logout": "still logged in"}, {"user": "kbrazil", "tty": "pts/0", "hostname": "192.168.71.1", "login": "Wed May 27 12:47", "logout": "16:09", "duration": "03:22"}, {"user": "kbrazil", "tty": "ttyv0", "hostname": null, "login": "Wed May 27 12:46", "logout": "12:48", "duration": "00:01"}, {"user": "boot time", "tty": null, "hostname": null, "login": "Wed May 27 12:46"}]

9
tests/fixtures/freebsd12/last.out vendored Normal file
View File

@@ -0,0 +1,9 @@
kbrazil pts/0 192.168.71.1 Thu May 28 21:29 still logged in
kbrazil pts/0 192.168.71.1 Thu May 28 04:13 - 21:29 (17:16)
kbrazil pts/0 192.168.71.1 Wed May 27 16:09 - 04:13 (12:03)
root ttyv0 Wed May 27 12:48 still logged in
kbrazil pts/0 192.168.71.1 Wed May 27 12:47 - 16:09 (03:22)
kbrazil ttyv0 Wed May 27 12:46 - 12:48 (00:01)
boot time Wed May 27 12:46
utx.log begins Wed May 27 12:46:05 PDT 2020

View File

@@ -0,0 +1 @@
[{"tcpcb": "fffff80003df43d0", "proto": "tcp4", "recv_q": 0, "send_q": 0, "local_address": "192.168.71.1", "foreign_address": "192.168.71.1", "state": "ESTABLISHED", "kind": "network", "local_port": "ssh", "foreign_port": "56321", "transport_protocol": "tcp", "network_protocol": "ipv4", "foreign_port_num": 56321}, {"tcpcb": "fffff80003df47a0", "proto": "tcp4", "recv_q": 0, "send_q": 0, "local_address": "localhost", "foreign_address": "*", "state": "LISTEN", "kind": "network", "local_port": "smtp", "foreign_port": "*", "transport_protocol": "tcp", "network_protocol": "ipv4"}, {"tcpcb": "fffff80003df4b70", "proto": "tcp4", "recv_q": 0, "send_q": 0, "local_address": "*", "foreign_address": "*", "state": "LISTEN", "kind": "network", "local_port": "ssh", "foreign_port": "*", "transport_protocol": "tcp", "network_protocol": "ipv4"}, {"tcpcb": "fffff80003df0000", "proto": "tcp6", "recv_q": 0, "send_q": 0, "local_address": "*", "foreign_address": "*", "state": "LISTEN", "kind": "network", "local_port": "ssh", "foreign_port": "*", "transport_protocol": "tcp", "network_protocol": "ipv6"}, {"tcpcb": "fffff80003ae81e8", "proto": "udp4", "recv_q": 0, "send_q": 0, "local_address": "*", "foreign_address": "*", "kind": "network", "local_port": "syslog", "foreign_port": "*", "transport_protocol": "udp", "network_protocol": "ipv4"}, {"tcpcb": "fffff80003ae87a0", "proto": "udp6", "recv_q": 0, "send_q": 0, "local_address": "*", "foreign_address": "*", "kind": "network", "local_port": "syslog", "foreign_port": "*", "transport_protocol": "udp", "network_protocol": "ipv6"}, {"address": "fffff80003ab6100", "type": "stream", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6200", "refs": "0", "nextref": "0", "kind": "socket"}, {"address": "fffff80003ab6200", "type": "stream", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6100", "refs": "0", "nextref": "0", "kind": "socket"}, {"address": "fffff80003ab6300", "type": "stream", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "0", "refs": "0", "nextref": "0", "kind": "socket"}, {"address": "fffff80003ab6800", "type": "stream", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6900", "refs": "0", "nextref": "0", "kind": "socket"}, {"address": "fffff80003ab6900", "type": "stream", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6800", "refs": "0", "nextref": "0", "kind": "socket"}, {"address": "fffff80003abf000", "type": "stream", "recv_q": 0, "send_q": 0, "unix_inode": "fffff80003c581e0", "conn": "0", "refs": "0", "nextref": "0", "addr": "/var/run/devd.pipe", "kind": "socket"}, {"address": "fffff80003ac3e00", "type": "dgram", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6c00", "refs": "0", "nextref": "fffff80003ab6400", "kind": "socket"}, {"address": "fffff80003ab6400", "type": "dgram", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6c00", "refs": "0", "nextref": "fffff80003ab6000", "kind": "socket"}, {"address": "fffff80003ab6000", "type": "dgram", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6c00", "refs": "0", "nextref": "fffff80003ab6600", "kind": "socket"}, {"address": "fffff80003ab6500", "type": "dgram", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6d00", "refs": "0", "nextref": "0", "kind": "socket"}, {"address": "fffff80003ab6600", "type": "dgram", "recv_q": 0, "send_q": 0, "unix_inode": "0", "conn": "fffff80003ab6c00", "refs": "0", "nextref": "0", "kind": "socket"}, {"address": "fffff80003ab6c00", "type": "dgram", "recv_q": 0, "send_q": 0, "unix_inode": "fffff80003c59d20", "conn": "0", "refs": "fffff80003ac3e00", "nextref": "0", "addr": "/var/run/logpriv", "kind": "socket"}, {"address": "fffff80003ab6d00", "type": "dgram", "recv_q": 0, "send_q": 0, "unix_inode": "fffff80003c67000", "conn": "0", "refs": "fffff80003ab6500", "nextref": "0", "addr": "/var/run/log", "kind": "socket"}, {"address": "fffff80003ab6e00", "type": "seqpac", "recv_q": 0, "send_q": 0, "unix_inode": "fffff80003c58000", "conn": "0", "refs": "0", "nextref": "0", "addr": "/var/run/devd.seqpacket.pipe", "kind": "socket"}]

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