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

Compare commits

...

148 Commits

Author SHA1 Message Date
Kelly Brazil
a4a53f8b3b Merge pull request #368 from kellyjonbrazil/dev
Dev v1.23.0
2023-02-27 15:13:57 -08:00
Kelly Brazil
743e1ae90f doc update 2023-02-27 15:01:00 -08:00
Kelly Brazil
dbcff80907 add zpool tests 2023-02-27 13:44:16 -08:00
Kelly Brazil
6e10965aed doc update 2023-02-21 17:19:17 -08:00
Kelly Brazil
0c8c4a9c53 formatting 2023-02-21 17:18:24 -08:00
Kelly Brazil
7632541a1e Merge pull request #365 from tzeikob/xrandr-edid-364
Modify the xrandr parser to extract display model info from EDID #364
2023-02-21 17:04:46 -08:00
Kelly Brazil
a213ad9a85 doc update 2023-02-21 15:50:09 -08:00
Kelly Brazil
15ac5a9004 fix for crontab files with only shortcut entries 2023-02-21 15:44:55 -08:00
Jake Ob
0658668eb0 Fix broken TypedDict compatibility with older versions of python
By missing to declare the Model as a generic dictionary type we cause
the script to fail because the TypedDict is not supported by older
versions of python (e.g. 3.6, 3.7).
2023-02-21 14:43:10 +02:00
Kelly Brazil
ccef69ac37 add ubuntu16 tests 2023-02-20 12:04:45 -08:00
Kelly Brazil
64676fda2e fix for older linux style output 2023-02-20 11:56:20 -08:00
Jake Ob
6ea2d776ae Add unit tests for xrandr --properties outputs 2023-02-17 16:25:41 +02:00
Jake Ob
98ced9616c Modify xrandr parser to extract model info from EDID (#364)
This change will make the xrandr parser to work also with outputs from
`xrandr --properties`, where the option `--properties` enables xrandr
to return the EDID data of each output device. The parser is expected
to remain backward compatible for regular xrandr outputs. The extra info
that will be added to every device object is:

```json
{
  "model_name": "str",
  "product_id": "str",
  "serial_number": "str"
}
```

Because the EDID data come encoded in a hexadecimal string, we have to
decode them by vendoring the pyedid (https://github.com/jojonas/pyedid) library.

Fixes: #364
2023-02-17 16:09:50 +02:00
Kelly Brazil
ae19183803 add test for is_current fix 2023-02-13 18:17:14 -08:00
Kelly Brazil
8536514baf add test for output with extra spaces that caused infinite loops 2023-02-13 18:09:10 -08:00
Kelly Brazil
81982a9f79 doc updagte 2023-02-12 18:30:31 -08:00
Kelly Brazil
1acbb2f096 Merge pull request #361 from tzeikob/reflect-360
Move the reflect value in its own key (xrandr) #360
2023-02-12 18:28:37 -08:00
Jake Ob
80fb4d40a5 Add unit test to assert devices in reflect mode 2023-02-09 21:22:34 +02:00
Kelly Brazil
c972dd1aac Merge pull request #362 from jpmat296/master
Improve accepted formats in proc_pid_smaps
2023-02-09 09:51:26 -08:00
Jean-Pierre Matsumoto
9dde65c25c Improve accepted formats in proc_pid_smaps 2023-02-07 14:57:15 +01:00
Jake Ob
7486b0c7cd Move the reflect value in its own key (xrandr)
Currently the reflect value of a device is included in the rotation
key as a combination of both rotation and reflection (e.g. normal X axis).
With this modification we move the reflection value to its own key in the
JSON document like so:

```json
{
  ...
  "rotation": "...",
  "reflection": "...,
  ...
}
```

Fixes: issue #360
2023-02-07 13:02:21 +02:00
Kelly Brazil
5c7a520a0b formatting 2023-02-05 13:04:32 -08:00
Kelly Brazil
193ddf71f8 fix typos 2023-02-05 09:52:59 -08:00
Kelly Brazil
c46fe9816c doc update 2023-02-05 09:49:32 -08:00
Kelly Brazil
aada5f0794 add int conversions 2023-02-04 19:04:43 -08:00
Kelly Brazil
5c7bf363a6 add schema and docs 2023-02-04 18:51:31 -08:00
Kelly Brazil
00b74be540 version bump 2023-02-04 17:37:56 -08:00
Kelly Brazil
96cb01f57a fix zpool-status for multi-line fields 2023-02-04 17:37:48 -08:00
Kelly Brazil
adf5f403ae regex fix for infinite loop? 2023-02-04 14:37:03 -08:00
Kelly Brazil
1c09c95c71 doc update 2023-02-04 11:26:41 -08:00
Kelly Brazil
64f442d743 fix for is_current? 2023-02-04 11:20:48 -08:00
Kelly Brazil
cd8d43446b add zpool-status parser 2023-02-02 15:57:15 -08:00
Kelly Brazil
7361eac1a4 formatting 2023-01-31 17:02:04 -08:00
Kelly Brazil
9d41f0a938 doc update 2023-01-31 17:00:42 -08:00
Kelly Brazil
00274c15df add process conversions 2023-01-31 16:52:56 -08:00
Kelly Brazil
d0b8a91f94 add zpool-iostat parser 2023-01-31 11:59:19 -08:00
Kelly Brazil
098e8dbef6 doc update 2023-01-31 11:59:06 -08:00
Kelly Brazil
ec29b8bbc6 add test for acpi fix for never fully discharge state 2023-01-31 09:51:23 -08:00
Kelly Brazil
1d8f83b8c6 add ver parser tests 2023-01-31 09:44:23 -08:00
Kelly Brazil
12c4419c6a doc update 2023-01-31 08:30:37 -08:00
Kelly Brazil
b134c53f33 add ssh_conf tests 2023-01-31 08:29:43 -08:00
Kelly Brazil
23ff19fdb5 add slicer tests 2023-01-31 06:48:37 -08:00
Kelly Brazil
7a43ba631b fix tests for slice info metadata 2023-01-30 08:59:54 -08:00
Kelly Brazil
2beb26f3e3 add slice info to Metadata output 2023-01-30 08:52:22 -08:00
Kelly Brazil
4b55f49e99 formatting 2023-01-30 08:11:21 -08:00
Kelly Brazil
c0239a771c clarify parser argument quote wrapping code 2023-01-27 16:28:55 -08:00
Kelly Brazil
bc4738e900 don't quote spaces in command arguments 2023-01-27 15:58:01 -08:00
Kelly Brazil
1c76caf59b formatting 2023-01-27 15:22:36 -08:00
Kelly Brazil
0679951d4a add <nobr> tag 2023-01-27 15:17:41 -08:00
Kelly Brazil
df7aee9e4b doc update 2023-01-27 14:47:08 -08:00
Kelly Brazil
a8d97a2521 add slice info to help and doc update 2023-01-27 11:41:21 -08:00
Kelly Brazil
9370b336d8 add slice to man page 2023-01-27 11:31:16 -08:00
Kelly Brazil
118f98222a change restapi link 2023-01-27 08:15:53 -08:00
Kelly Brazil
dc997821f4 ad jc-api demo link 2023-01-27 08:12:07 -08:00
Kelly Brazil
aef0fdb435 formatting 2023-01-27 08:02:07 -08:00
Kelly Brazil
21ee3c0e78 clarify slice usage 2023-01-27 08:01:32 -08:00
Kelly Brazil
d6c665f74b add both negative slice description 2023-01-26 17:06:07 -08:00
Kelly Brazil
79305a50d0 formatting 2023-01-26 16:59:15 -08:00
Kelly Brazil
e758aa41ef formatting 2023-01-26 16:49:27 -08:00
Kelly Brazil
910cb34b09 formatting 2023-01-26 16:49:09 -08:00
Kelly Brazil
976fea4eb2 Formatting 2023-01-26 16:46:54 -08:00
Kelly Brazil
81447ec9e6 formatting 2023-01-26 16:44:26 -08:00
Kelly Brazil
61b8e9f7b5 doc update 2023-01-26 16:24:32 -08:00
Kelly Brazil
8e86a04448 add slicing info 2023-01-26 16:18:54 -08:00
Kelly Brazil
f23715a783 formatting 2023-01-26 08:53:50 -08:00
Kelly Brazil
fa693a8bb1 formatting 2023-01-26 08:53:06 -08:00
Kelly Brazil
103168c25b add integer conversions 2023-01-26 08:48:59 -08:00
Kelly Brazil
ae7ed4eec5 doc update 2023-01-26 08:48:46 -08:00
Kelly Brazil
caa516db72 formatting 2023-01-25 16:59:49 -08:00
Kelly Brazil
f05c3d6113 docstring update 2023-01-25 16:57:15 -08:00
Kelly Brazil
5d872b1535 docstring update: add see also section. 2023-01-25 16:30:20 -08:00
Kelly Brazil
1559cd9f5c fixup patch numbers 2023-01-25 16:13:33 -08:00
Kelly Brazil
a5a87c7da1 add ver parser 2023-01-25 15:58:27 -08:00
Kelly Brazil
0e6cec62c1 remove unneeded slicer syntax check 2023-01-23 16:53:06 -08:00
Kelly Brazil
3bb1d89165 fix multiple slicer warnings 2023-01-23 16:50:41 -08:00
Kelly Brazil
f5f5f102fb full functioning slicer feature 2023-01-23 16:13:17 -08:00
Kelly Brazil
0c82fe7e4d add slicer functionality 2023-01-23 13:57:48 -08:00
Kelly Brazil
13ffe8a84d doc update 2023-01-22 12:44:16 -08:00
Kelly Brazil
4c6eebaa33 doc update 2023-01-22 10:37:27 -08:00
Kelly Brazil
f8fbb2dce2 use derivative of sshd_conf parser instead of paramiko 2023-01-22 10:36:29 -08:00
Kelly Brazil
dbfe682674 fix for never fully discharging state 2023-01-22 08:57:50 -08:00
Kelly Brazil
2a148d44a1 add host to objects 2023-01-21 17:00:22 -08:00
Kelly Brazil
0648d2e9e3 don't interpolate local host values 2023-01-21 16:37:27 -08:00
Kelly Brazil
ac9128fa0c add ssh-conf parser 2023-01-21 13:10:45 -08:00
Kelly Brazil
4d7a872670 version bump 2023-01-21 10:56:45 -08:00
Kelly Brazil
07f716b254 Merge pull request #355 from kellyjonbrazil/master
sync to dev
2023-01-21 10:53:03 -08:00
Kelly Brazil
b0d6a7307c Merge pull request #353 from kellyjonbrazil/dev
Dev v1.22.5
2023-01-11 14:39:13 -08:00
Kelly Brazil
53ad793ff8 doc update 2023-01-11 11:17:47 -08:00
Kelly Brazil
ea4332d8e4 add ini-dup tests 2023-01-10 16:06:05 -08:00
Kelly Brazil
5c5ff9324f update multi-line value behavior 2023-01-09 08:31:21 -08:00
Kelly Brazil
2ab6352fdb doc update 2023-01-08 19:54:32 -08:00
Kelly Brazil
56dcbaf40c doc update 2023-01-08 19:53:21 -08:00
Kelly Brazil
029b5abcac add ini-dup parser 2023-01-08 19:46:13 -08:00
Kelly Brazil
32521ac91a doc update 2023-01-08 10:55:48 -08:00
Kelly Brazil
bf88407902 remove special DEFAULT section handling 2023-01-08 10:55:44 -08:00
Kelly Brazil
63cf47db63 doc update 2023-01-06 13:37:34 -08:00
Kelly Brazil
9970866b3b Merge pull request #350 from davemq/arp_aix
AIX ARP support
2023-01-06 13:34:44 -08:00
Dave Marquardt
d61941b276 Merge branch 'dev' into arp_aix 2023-01-06 15:14:39 -06:00
Dave Marquardt
081fdb8026 Updated AIX ARP test comments 2023-01-06 15:13:03 -06:00
Dave Marquardt
f64dfbf79d Update comments about AIX ARP 2023-01-06 15:10:02 -06:00
Dave Marquardt
5fb73f4ad5 Added AIX support for ARP, along with AIX ARP test support 2023-01-06 14:53:50 -06:00
Dave Marquardt
1c16d32420 Added test data for AIX ARP 2023-01-06 14:51:09 -06:00
Kelly Brazil
e367e0d714 doc update 2023-01-06 09:28:13 -08:00
Kelly Brazil
4046649e32 toml tests and doc update 2023-01-06 09:11:00 -08:00
Kelly Brazil
77fcfe439c remove unused list 2023-01-06 08:50:03 -08:00
Kelly Brazil
71d1355419 formatting 2023-01-06 08:36:48 -08:00
Kelly Brazil
d1b270f336 formatting 2023-01-06 08:26:42 -08:00
Dave Marquardt
9190a08332 More AIX permanent ARP entry fixes 2023-01-06 10:25:04 -06:00
Dave Marquardt
3fae50e305 Handle permanent ARP entries on AIX 2023-01-06 10:21:32 -06:00
Dave Marquardt
3582497ed4 Fix interface parsing for incomplete ARP entry on AIX 2023-01-06 10:16:13 -06:00
Dave Marquardt
19a67daabf rewrite check for incomplete ARP entries 2023-01-06 10:12:50 -06:00
Dave Marquardt
02a7e5fd8a Updated ARP incomplete handling for AIX 2023-01-06 10:08:33 -06:00
Dave Marquardt
df72b16022 Start to fix AIX ARP support 2023-01-06 10:00:58 -06:00
Kelly Brazil
61bdc13810 doc update 2023-01-05 17:05:03 -08:00
Kelly Brazil
1350a34316 beautify process function 2023-01-05 16:59:22 -08:00
Kelly Brazil
7cfe68b96a beautify remove_quotes function 2023-01-05 16:55:53 -08:00
Kelly Brazil
7a93a61f54 move unsectioned k/v's to the top level 2023-01-05 15:44:21 -08:00
Kelly Brazil
362977e598 comment unused objects 2023-01-05 14:26:01 -08:00
Kelly Brazil
5cac897beb zipinfo fix for paths with spaces 2023-01-05 12:00:20 -08:00
Kelly Brazil
cad94cc6b3 formatting and doc update 2023-01-05 11:46:14 -08:00
Kelly Brazil
fe4e429e85 Merge pull request #349 from davemq/mount_aix
Add AIX mount support
2023-01-05 11:39:42 -08:00
Kelly Brazil
d83b10e2d5 doc update 2023-01-05 11:27:49 -08:00
Kelly Brazil
bd2a757fcd more refactoring 2023-01-05 11:27:00 -08:00
Dave Marquardt
358b69a4cb Merge branch 'dev' into mount_aix 2023-01-05 11:06:00 -06:00
Dave Marquardt
08821a1454 Removed unneeded imports from mount parser 2023-01-05 10:57:23 -06:00
Kelly Brazil
90277c1d87 formatting 2023-01-05 08:54:40 -08:00
Dave Marquardt
a51e702f77 Added AIX mount test 2023-01-05 10:53:02 -06:00
Dave Marquardt
466e37128b Merge branch 'dev' into mount_aix 2023-01-05 10:32:47 -06:00
Dave Marquardt
f1ea61388f Added AIX mount support 2023-01-05 10:28:01 -06:00
Kelly Brazil
72c11fda3a more refactoring 2023-01-05 08:00:18 -08:00
Kelly Brazil
d6645983ef more refactoring 2023-01-04 20:54:31 -08:00
Kelly Brazil
01e911ecdb remove more type annotations 2023-01-04 19:17:29 -08:00
Kelly Brazil
12c0b3d889 remove more type annotations 2023-01-04 19:13:12 -08:00
Kelly Brazil
c1075e40b7 remove more type hinting for python 3.6 2023-01-04 19:09:41 -08:00
Kelly Brazil
fabe3f01b7 remove type annotations for python 3.6 compatibility 2023-01-04 19:00:49 -08:00
Kelly Brazil
3a2ff61899 fix lsusb for extra hub port info and add videocontrol and videostreaming sections 2023-01-04 18:27:51 -08:00
Kelly Brazil
aeff94d272 add toml parser 2023-01-04 18:26:13 -08:00
Kelly Brazil
c94f5d83fa fix kv and ini parsers to only remove 1 quote from beginning and end 2023-01-03 13:26:52 -08:00
Kelly Brazil
b0756fa0f9 doc update 2023-01-03 13:05:51 -08:00
Kelly Brazil
6c85abd57b doc update 2023-01-03 13:04:13 -08:00
Kelly Brazil
7659ae94bd formatting 2023-01-03 12:59:10 -08:00
Kelly Brazil
e306e81e43 formatting 2023-01-03 12:57:04 -08:00
Kelly Brazil
6d5768b26b simplify code and doc update 2023-01-03 12:55:38 -08:00
Kelly Brazil
cee9f8bf32 remove duplicate code. add tests 2023-01-03 12:44:12 -08:00
Kelly Brazil
b8e2678c01 doc update 2023-01-03 12:30:23 -08:00
Kelly Brazil
b0fe96ed03 doc update 2023-01-03 12:28:26 -08:00
Kelly Brazil
bb65ec380b separate kv and ini parsers. add fake top-level section in ini files that are missing it. 2023-01-03 12:11:05 -08:00
Kelly Brazil
ae6248227b version bump 2023-01-03 11:53:57 -08:00
138 changed files with 12767 additions and 761 deletions

View File

@@ -1,5 +1,34 @@
jc changelog
20230227 v1.23.0
- Add input slicing as a `jc` command-line option
- Add `ssh` configuration file parser
- Add `ver` Version string parser
- Add `zpool iostat` command parser
- Add `zpool status` command parser
- Fix `acpi` command parser for "will never fully discharge" battery state
- Fix `crontab` and `crontab-u` command and file parsers for cases where only
shortcut schedule items exist
- Fix `ifconfig` command parser for older-style linux output
- Fix `xrandr` command parser for proper `is_current` output
- Fix `xrandr` command parser for infinite loop with some device configurations
- Add `reflection` key to `xrandr` parser schema
- Add display model info from EDID to `xrandr` parser
- Add `MPX-specific VMA` support for VM Flags in `/proc/<pid>/smaps` parser
20230111 v1.22.5
- Add TOML file parser
- Add INI with duplicate key support file parser
- Add AIX support for the `arp` command parser
- Add AIX support for the `mount` command parser
- Fix `lsusb` command parser when extra hub port status information is output
- Refactor `lsusb` command parser for more code reuse
- Fix INI file parser to include top-level values with no section header
- Fix INI file parser to not specially handle the [DEFAULT] section
- Fix INI file and Key/Value parsers to only remove one quotation mark from the
beginning and end of values.
- Update copyright dates
20221230 v1.22.4
- Add `iwconfig` command parser
- Add NeXTSTEP format support to the PLIST file parser

View File

@@ -1636,21 +1636,21 @@ cat example.ini | jc --ini -p
```
```json
{
"bitbucket.org": {
"ServeraLiveInterval": "45",
"DEFAULT": {
"ServerAliveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "yes",
"ForwardX11": "yes"
},
"bitbucket.org": {
"User": "hg"
},
"topsecret.server.com": {
"ServeraLiveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "no",
"Port": "50022"
"Port": "50022",
"ForwardX11": "no"
}
}
```
### iostat
```bash

372
README.md
View File

@@ -3,7 +3,7 @@
> Check out the `jc` Python [package documentation](https://github.com/kellyjonbrazil/jc/tree/master/docs) for developers
> Try the `jc` [web demo](https://jc-web.onrender.com/)
> Try the `jc` [web demo](https://jc-web.onrender.com/) and [REST API](https://github.com/kellyjonbrazil/jc-restapi)
> JC is [now available](https://galaxy.ansible.com/community/general) as an
Ansible filter plugin in the `community.general` collection. See this
@@ -44,8 +44,8 @@ $ jc dig example.com | jq -r '.[].answer[].data'
93.184.216.34
```
`jc` can also be used as a python library. In this case the output will be
a python dictionary, a list of dictionaries, or even a
`jc` can also be used as a python library. In this case the returned value
will be a python dictionary, a list of dictionaries, or even a
[lazy iterable of dictionaries](#using-streaming-parsers-as-python-modules)
instead of JSON:
```python
@@ -133,9 +133,9 @@ on Github.
`jc` accepts piped input from `STDIN` and outputs a JSON representation of the
previous command's output to `STDOUT`.
```bash
COMMAND | jc [OPTIONS] PARSER
cat FILE | jc [OPTIONS] PARSER
echo STRING | jc [OPTIONS] PARSER
COMMAND | jc [SLICE] [OPTIONS] PARSER
cat FILE | jc [SLICE] [OPTIONS] PARSER
echo STRING | jc [SLICE] [OPTIONS] PARSER
```
Alternatively, the "magic" syntax can be used by prepending `jc` to the command
@@ -143,8 +143,8 @@ to be converted or in front of the absolute path for Proc files. Options can be
passed to `jc` immediately before the command or Proc file path is given.
(Note: command aliases and shell builtins are not supported)
```bash
jc [OPTIONS] COMMAND
jc [OPTIONS] /proc/<path-to-procfile>
jc [SLICE] [OPTIONS] COMMAND
jc [SLICE] [OPTIONS] /proc/<path-to-procfile>
```
The JSON output can be compact (default) or pretty formatted with the `-p`
@@ -154,141 +154,147 @@ option.
| Argument | Command or Filetype | Documentation |
|-------------------|---------------------------------------------------------|----------------------------------------------------------------------------|
| ` --acpi` | `acpi` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/acpi) |
| ` --airport` | `airport -I` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/airport) |
| ` --airport-s` | `airport -s` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/airport_s) |
| ` --arp` | `arp` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/arp) |
| ` --asciitable` | ASCII and Unicode table parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/asciitable) |
| ` --asciitable-m` | multi-line ASCII and Unicode table parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/asciitable_m) |
| ` --blkid` | `blkid` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/blkid) |
| ` --cbt` | `cbt` (Google Bigtable) command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cbt) |
| ` --cef` | CEF string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cef) |
| ` --cef-s` | CEF string streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cef_s) |
| ` --chage` | `chage --list` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/chage) |
| ` --cksum` | `cksum` and `sum` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cksum) |
| ` --clf` | Common and Combined Log Format file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/clf) |
| ` --clf-s` | Common and Combined Log Format file streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/clf_s) |
| ` --crontab` | `crontab` command and file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/crontab) |
| ` --crontab-u` | `crontab` file parser with user support | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/crontab_u) |
| ` --csv` | CSV file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/csv) |
| ` --csv-s` | CSV file streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/csv_s) |
| ` --date` | `date` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/date) |
| ` --datetime-iso` | ISO 8601 Datetime string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/datetime_iso) |
| ` --df` | `df` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/df) |
| ` --dig` | `dig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/dig) |
| ` --dir` | `dir` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/dir) |
| ` --dmidecode` | `dmidecode` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/dmidecode) |
| ` --dpkg-l` | `dpkg -l` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/dpkg_l) |
| ` --du` | `du` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/du) |
| `--acpi` | `acpi` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/acpi) |
| `--airport` | `airport -I` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/airport) |
| `--airport-s` | `airport -s` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/airport_s) |
| `--arp` | `arp` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/arp) |
| `--asciitable` | ASCII and Unicode table parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/asciitable) |
| `--asciitable-m` | multi-line ASCII and Unicode table parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/asciitable_m) |
| `--blkid` | `blkid` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/blkid) |
| `--cbt` | `cbt` (Google Bigtable) command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cbt) |
| `--cef` | CEF string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cef) |
| `--cef-s` | CEF string streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cef_s) |
| `--chage` | `chage --list` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/chage) |
| `--cksum` | `cksum` and `sum` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/cksum) |
| `--clf` | Common and Combined Log Format file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/clf) |
| `--clf-s` | Common and Combined Log Format file streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/clf_s) |
| `--crontab` | `crontab` command and file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/crontab) |
| `--crontab-u` | `crontab` file parser with user support | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/crontab_u) |
| `--csv` | CSV file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/csv) |
| `--csv-s` | CSV file streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/csv_s) |
| `--date` | `date` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/date) |
| `--datetime-iso` | ISO 8601 Datetime string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/datetime_iso) |
| `--df` | `df` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/df) |
| `--dig` | `dig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/dig) |
| `--dir` | `dir` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/dir) |
| `--dmidecode` | `dmidecode` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/dmidecode) |
| `--dpkg-l` | `dpkg -l` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/dpkg_l) |
| `--du` | `du` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/du) |
| `--email-address` | Email Address string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/email_address) |
| ` --env` | `env` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/env) |
| ` --file` | `file` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/file) |
| ` --findmnt` | `findmnt` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/findmnt) |
| ` --finger` | `finger` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/finger) |
| ` --free` | `free` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/free) |
| ` --fstab` | `/etc/fstab` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/fstab) |
| ` --git-log` | `git log` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/git_log) |
| ` --git-log-s` | `git log` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/git_log_s) |
| `--env` | `env` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/env) |
| `--file` | `file` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/file) |
| `--findmnt` | `findmnt` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/findmnt) |
| `--finger` | `finger` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/finger) |
| `--free` | `free` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/free) |
| `--fstab` | `/etc/fstab` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/fstab) |
| `--git-log` | `git log` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/git_log) |
| `--git-log-s` | `git log` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/git_log_s) |
| `--git-ls-remote` | `git ls-remote` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/git_ls_remote) |
| ` --gpg` | `gpg --with-colons` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/gpg) |
| ` --group` | `/etc/group` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/group) |
| ` --gshadow` | `/etc/gshadow` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/gshadow) |
| ` --hash` | `hash` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/hash) |
| ` --hashsum` | hashsum command parser (`md5sum`, `shasum`, etc.) | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/hashsum) |
| ` --hciconfig` | `hciconfig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/hciconfig) |
| ` --history` | `history` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/history) |
| ` --hosts` | `/etc/hosts` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/hosts) |
| ` --id` | `id` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/id) |
| ` --ifconfig` | `ifconfig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ifconfig) |
| ` --ini` | INI file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ini) |
| ` --iostat` | `iostat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iostat) |
| ` --iostat-s` | `iostat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iostat_s) |
| ` --ip-address` | IPv4 and IPv6 Address string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ip_address) |
| ` --iptables` | `iptables` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iptables) |
| ` --iw-scan` | `iw dev [device] scan` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iw_scan) |
| ` --iwconfig` | `iwconfig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iwconfig) |
| ` --jar-manifest` | Java MANIFEST.MF file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/jar_manifest) |
| ` --jobs` | `jobs` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/jobs) |
| ` --jwt` | JWT string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/jwt) |
| ` --kv` | Key/Value file and string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/kv) |
| ` --last` | `last` and `lastb` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/last) |
| ` --ls` | `ls` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ls) |
| ` --ls-s` | `ls` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ls_s) |
| ` --lsblk` | `lsblk` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsblk) |
| ` --lsmod` | `lsmod` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsmod) |
| ` --lsof` | `lsof` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsof) |
| ` --lspci` | `lspci -mmv` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lspci) |
| ` --lsusb` | `lsusb` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsusb) |
| ` --m3u` | M3U and M3U8 file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/m3u) |
| ` --mdadm` | `mdadm` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mdadm) |
| ` --mount` | `mount` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mount) |
| ` --mpstat` | `mpstat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mpstat) |
| ` --mpstat-s` | `mpstat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mpstat_s) |
| ` --netstat` | `netstat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/netstat) |
| ` --nmcli` | `nmcli` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/nmcli) |
| ` --ntpq` | `ntpq -p` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ntpq) |
| ` --openvpn` | openvpn-status.log file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/openvpn) |
| ` --os-prober` | `os-prober` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/os_prober) |
| ` --passwd` | `/etc/passwd` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/passwd) |
| ` --pci-ids` | `pci.ids` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pci_ids) |
| ` --pgpass` | PostgreSQL password file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pgpass) |
| ` --pidstat` | `pidstat -H` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pidstat) |
| ` --pidstat-s` | `pidstat -H` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pidstat_s) |
| ` --ping` | `ping` and `ping6` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ping) |
| ` --ping-s` | `ping` and `ping6` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ping_s) |
| ` --pip-list` | `pip list` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pip_list) |
| ` --pip-show` | `pip show` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pip_show) |
| ` --plist` | PLIST file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/plist) |
| ` --postconf` | `postconf -M` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/postconf) |
| ` --proc` | `/proc/` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/proc) |
| ` --ps` | `ps` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ps) |
| ` --route` | `route` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/route) |
| ` --rpm-qi` | `rpm -qi` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rpm_qi) |
| ` --rsync` | `rsync` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rsync) |
| ` --rsync-s` | `rsync` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rsync_s) |
| ` --semver` | Semantic Version string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/semver) |
| ` --sfdisk` | `sfdisk` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/sfdisk) |
| ` --shadow` | `/etc/shadow` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/shadow) |
| ` --ss` | `ss` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ss) |
| ` --sshd-conf` | sshd config file and `sshd -T` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/sshd_conf) |
| ` --stat` | `stat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/stat) |
| ` --stat-s` | `stat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/stat_s) |
| ` --sysctl` | `sysctl` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/sysctl) |
| ` --syslog` | Syslog RFC 5424 string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/syslog) |
| ` --syslog-s` | Syslog RFC 5424 string streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/syslog_s) |
| ` --syslog-bsd` | Syslog RFC 3164 string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/syslog_bsd) |
| ` --syslog-bsd-s` | Syslog RFC 3164 string streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/syslog_bsd_s) |
| ` --systemctl` | `systemctl` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systemctl) |
| ` --systemctl-lj` | `systemctl list-jobs` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systemctl_lj) |
| ` --systemctl-ls` | `systemctl list-sockets` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systemctl_ls) |
| `--gpg` | `gpg --with-colons` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/gpg) |
| `--group` | `/etc/group` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/group) |
| `--gshadow` | `/etc/gshadow` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/gshadow) |
| `--hash` | `hash` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/hash) |
| `--hashsum` | hashsum command parser (`md5sum`, `shasum`, etc.) | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/hashsum) |
| `--hciconfig` | `hciconfig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/hciconfig) |
| `--history` | `history` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/history) |
| `--hosts` | `/etc/hosts` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/hosts) |
| `--id` | `id` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/id) |
| `--ifconfig` | `ifconfig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ifconfig) |
| `--ini` | INI file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ini) |
| `--ini-dup` | INI with duplicate key file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ini_dup) |
| `--iostat` | `iostat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iostat) |
| `--iostat-s` | `iostat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iostat_s) |
| `--ip-address` | IPv4 and IPv6 Address string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ip_address) |
| `--iptables` | `iptables` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iptables) |
| `--iw-scan` | `iw dev [device] scan` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iw_scan) |
| `--iwconfig` | `iwconfig` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/iwconfig) |
| `--jar-manifest` | Java MANIFEST.MF file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/jar_manifest) |
| `--jobs` | `jobs` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/jobs) |
| `--jwt` | JWT string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/jwt) |
| `--kv` | Key/Value file and string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/kv) |
| `--last` | `last` and `lastb` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/last) |
| `--ls` | `ls` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ls) |
| `--ls-s` | `ls` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ls_s) |
| `--lsblk` | `lsblk` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsblk) |
| `--lsmod` | `lsmod` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsmod) |
| `--lsof` | `lsof` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsof) |
| `--lspci` | `lspci -mmv` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lspci) |
| `--lsusb` | `lsusb` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/lsusb) |
| `--m3u` | M3U and M3U8 file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/m3u) |
| `--mdadm` | `mdadm` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mdadm) |
| `--mount` | `mount` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mount) |
| `--mpstat` | `mpstat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mpstat) |
| `--mpstat-s` | `mpstat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/mpstat_s) |
| `--netstat` | `netstat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/netstat) |
| `--nmcli` | `nmcli` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/nmcli) |
| `--ntpq` | `ntpq -p` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ntpq) |
| `--openvpn` | openvpn-status.log file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/openvpn) |
| `--os-prober` | `os-prober` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/os_prober) |
| `--passwd` | `/etc/passwd` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/passwd) |
| `--pci-ids` | `pci.ids` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pci_ids) |
| `--pgpass` | PostgreSQL password file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pgpass) |
| `--pidstat` | `pidstat -H` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pidstat) |
| `--pidstat-s` | `pidstat -H` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pidstat_s) |
| `--ping` | `ping` and `ping6` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ping) |
| `--ping-s` | `ping` and `ping6` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ping_s) |
| `--pip-list` | `pip list` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pip_list) |
| `--pip-show` | `pip show` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/pip_show) |
| `--plist` | PLIST file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/plist) |
| `--postconf` | `postconf -M` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/postconf) |
| `--proc` | `/proc/` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/proc) |
| `--ps` | `ps` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ps) |
| `--route` | `route` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/route) |
| `--rpm-qi` | `rpm -qi` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rpm_qi) |
| `--rsync` | `rsync` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rsync) |
| `--rsync-s` | `rsync` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/rsync_s) |
| `--semver` | Semantic Version string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/semver) |
| `--sfdisk` | `sfdisk` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/sfdisk) |
| `--shadow` | `/etc/shadow` file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/shadow) |
| `--ss` | `ss` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ss) |
| `--ssh-conf` | `ssh` config file and `ssh -G` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ssh_conf) |
| `--sshd-conf` | `sshd` config file and `sshd -T` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/sshd_conf) |
| `--stat` | `stat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/stat) |
| `--stat-s` | `stat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/stat_s) |
| `--sysctl` | `sysctl` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/sysctl) |
| `--syslog` | Syslog RFC 5424 string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/syslog) |
| `--syslog-s` | Syslog RFC 5424 string streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/syslog_s) |
| `--syslog-bsd` | Syslog RFC 3164 string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/syslog_bsd) |
| `--syslog-bsd-s` | Syslog RFC 3164 string streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/syslog_bsd_s) |
| `--systemctl` | `systemctl` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systemctl) |
| `--systemctl-lj` | `systemctl list-jobs` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systemctl_lj) |
| `--systemctl-ls` | `systemctl list-sockets` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systemctl_ls) |
| `--systemctl-luf` | `systemctl list-unit-files` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systemctl_luf) |
| ` --systeminfo` | `systeminfo` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systeminfo) |
| ` --time` | `/usr/bin/time` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/time) |
| ` --timedatectl` | `timedatectl status` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/timedatectl) |
| ` --timestamp` | Unix Epoch Timestamp string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/timestamp) |
| ` --top` | `top -b` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/top) |
| ` --top-s` | `top -b` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/top_s) |
| ` --tracepath` | `tracepath` and `tracepath6` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/tracepath) |
| ` --traceroute` | `traceroute` and `traceroute6` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/traceroute) |
| ` --udevadm` | `udevadm info` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/udevadm) |
| ` --ufw` | `ufw status` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw) |
| ` --ufw-appinfo` | `ufw app info [application]` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw_appinfo) |
| ` --uname` | `uname -a` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/uname) |
| `--systeminfo` | `systeminfo` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/systeminfo) |
| `--time` | `/usr/bin/time` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/time) |
| `--timedatectl` | `timedatectl status` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/timedatectl) |
| `--timestamp` | Unix Epoch Timestamp string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/timestamp) |
| `--toml` | TOML file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/toml) |
| `--top` | `top -b` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/top) |
| `--top-s` | `top -b` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/top_s) |
| `--tracepath` | `tracepath` and `tracepath6` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/tracepath) |
| `--traceroute` | `traceroute` and `traceroute6` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/traceroute) |
| `--udevadm` | `udevadm info` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/udevadm) |
| `--ufw` | `ufw status` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw) |
| `--ufw-appinfo` | `ufw app info [application]` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw_appinfo) |
| `--uname` | `uname -a` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/uname) |
| `--update-alt-gs` | `update-alternatives --get-selections` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/update_alt_gs) |
| ` --update-alt-q` | `update-alternatives --query` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/update_alt_q) |
| ` --upower` | `upower` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/upower) |
| ` --uptime` | `uptime` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/uptime) |
| ` --url` | URL string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/url) |
| ` --vmstat` | `vmstat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/vmstat) |
| ` --vmstat-s` | `vmstat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/vmstat_s) |
| ` --w` | `w` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/w) |
| ` --wc` | `wc` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/wc) |
| ` --who` | `who` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/who) |
| ` --x509-cert` | X.509 PEM and DER certificate file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/x509_cert) |
| ` --xml` | XML file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/xml) |
| ` --xrandr` | `xrandr` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/xrandr) |
| ` --yaml` | YAML file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/yaml) |
| ` --zipinfo` | `zipinfo` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/zipinfo) |
| `--update-alt-q` | `update-alternatives --query` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/update_alt_q) |
| `--upower` | `upower` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/upower) |
| `--uptime` | `uptime` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/uptime) |
| `--url` | URL string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/url) |
| `--ver` | Version string parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/ver) |
| `--vmstat` | `vmstat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/vmstat) |
| `--vmstat-s` | `vmstat` command streaming parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/vmstat_s) |
| `--w` | `w` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/w) |
| `--wc` | `wc` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/wc) |
| `--who` | `who` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/who) |
| `--x509-cert` | X.509 PEM and DER certificate file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/x509_cert) |
| `--xml` | XML file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/xml) |
| `--xrandr` | `xrandr` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/xrandr) |
| `--yaml` | YAML file parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/yaml) |
| `--zipinfo` | `zipinfo` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/zipinfo) |
| `--zpool-iostat` | `zpool iostat` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/zpool_iostat) |
| `--zpool-status` | `zpool status` command parser | [details](https://kellyjonbrazil.github.io/jc/docs/parsers/zpool_status) |
### Options
@@ -309,6 +315,54 @@ option.
| `-B` | `--bash-comp` | Generate Bash shell completion script ([more info](https://github.com/kellyjonbrazil/jc/wiki/Shell-Completions)) |
| `-Z` | `--zsh-comp` | Generate Zsh shell completion script ([more info](https://github.com/kellyjonbrazil/jc/wiki/Shell-Completions)) |
### Slice
Line slicing is supported using the `START:STOP` syntax similar to Python
slicing. This allows you to skip lines at the beginning and/or end of the
`STDIN` input you would like `jc` to convert.
`START` and `STOP` can be positive or negative integers or blank and allow
you to specify how many lines to skip and how many lines to process.
Positive and blank slices are the most memory efficient. Any negative
integers in the slice will use more memory.
For example, to skip the first and last line of the following text, you
could express the slice in a couple ways:
```bash
$ cat table.txt
### We want to skip this header ###
col1 col2
foo 1
bar 2
### We want to skip this footer ###
$ cat table.txt | jc 1:-1 --asciitable
[{"col1":"foo","col2":"1"},{"col1":"bar","col2":"2"}]
$ cat table.txt | jc 1:4 --asciitable
[{"col1":"foo","col2":"1"},{"col1":"bar","col2":"2"}]
```
In this example `1:-1` and `1:4` line slices provide the same output.
When using positive integers the index location of `STOP` is non-inclusive.
Positive slices count from the first line of the input toward the end
starting at `0` as the first line. Negative slices count from the last line
toward the beginning starting at `-1` as the last line. This is also the way
[Python's slicing](https://stackoverflow.com/questions/509211/understanding-slicing)
feature works.
Here is a breakdown of line slice options:
| Slice Notation | Input Lines Processed |
|----------------|--------------------------------------------------------------|
| `START:STOP` | lines `START` through `STOP - 1` |
| `START:` | lines `START` through the rest of the output |
| `:STOP` | lines from the beginning through `STOP - 1` |
| `-START:STOP` | `START` lines from the end through `STOP - 1` |
| `START:-STOP` | lines `START` through `STOP` lines from the end |
| `-START:-STOP` | `START` lines from the end through `STOP` lines from the end |
| `-START:` | `START` lines from the end through the rest of the output |
| `:-STOP` | lines from the beginning through `STOP` lines from the end |
| `:` | all lines |
### Exit Codes
Any fatal errors within `jc` will generate an exit code of `100`, otherwise the
exit code will be `0`.
@@ -756,37 +810,31 @@ ifconfig | jc -p --ifconfig # or: jc -p ifconfig
cat example.ini
```
```
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes
foo = fiz
bar = buz
[bitbucket.org]
User = hg
[section1]
fruit = apple
color = blue
[topsecret.server.com]
Port = 50022
ForwardX11 = no
[section2]
fruit = pear
color = green
```
```bash
cat example.ini | jc -p --ini
```
```json
{
"bitbucket.org": {
"ServeraLiveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "yes",
"User": "hg"
"foo": "fiz",
"bar": "buz",
"section1": {
"fruit": "apple",
"color": "blue"
},
"topsecret.server.com": {
"ServeraLiveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "no",
"Port": "50022"
"section2": {
"fruit": "pear",
"color": "green"
}
}
```
@@ -1242,4 +1290,4 @@ cat istio.yaml | jc -p --yaml
]
```
© 2019-2022 Kelly Brazil
© 2019-2023 Kelly Brazil

View File

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

View File

@@ -9,7 +9,7 @@ _jc() {
jc_help_options jc_help_options_describe \
jc_special_options jc_special_options_describe
jc_commands=(acpi airport arp blkid cbt chage cksum crontab date df dig dmidecode dpkg du env file findmnt finger free git gpg hciconfig id ifconfig iostat iptables iw iwconfig jobs last lastb ls lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat netstat nmcli ntpq os-prober pidstat ping ping6 pip pip3 postconf printenv ps route rpm rsync sfdisk sha1sum sha224sum sha256sum sha384sum sha512sum shasum ss sshd stat sum sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 udevadm ufw uname update-alternatives upower uptime vdir vmstat w wc who xrandr zipinfo)
jc_commands=(acpi airport arp blkid cbt chage cksum crontab date df dig dmidecode dpkg du env file findmnt finger free git gpg hciconfig id ifconfig iostat iptables iw iwconfig jobs last lastb ls lsblk lsmod lsof lspci lsusb md5 md5sum mdadm mount mpstat netstat nmcli ntpq os-prober pidstat ping ping6 pip pip3 postconf printenv ps route rpm rsync sfdisk sha1sum sha224sum sha256sum sha384sum sha512sum shasum ss ssh sshd stat sum sysctl systemctl systeminfo timedatectl top tracepath tracepath6 traceroute traceroute6 udevadm ufw uname update-alternatives upower uptime vdir vmstat w wc who xrandr zipinfo zpool)
jc_commands_describe=(
'acpi:run "acpi" command with magic syntax.'
'airport:run "airport" command with magic syntax.'
@@ -76,6 +76,7 @@ _jc() {
'sha512sum:run "sha512sum" command with magic syntax.'
'shasum:run "shasum" command with magic syntax.'
'ss:run "ss" command with magic syntax.'
'ssh:run "ssh" command with magic syntax.'
'sshd:run "sshd" command with magic syntax.'
'stat:run "stat" command with magic syntax.'
'sum:run "sum" command with magic syntax.'
@@ -101,8 +102,9 @@ _jc() {
'who:run "who" command with magic syntax.'
'xrandr:run "xrandr" command with magic syntax.'
'zipinfo:run "zipinfo" command with magic syntax.'
'zpool:run "zpool" command with magic syntax.'
)
jc_parsers=(--acpi --airport --airport-s --arp --asciitable --asciitable-m --blkid --cbt --cef --cef-s --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --date --datetime-iso --df --dig --dir --dmidecode --dpkg-l --du --email-address --env --file --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --hosts --id --ifconfig --ini --iostat --iostat-s --ip-address --iptables --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --last --ls --ls-s --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --netstat --nmcli --ntpq --openvpn --os-prober --passwd --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --plist --postconf --proc --proc-buddyinfo --proc-consoles --proc-cpuinfo --proc-crypto --proc-devices --proc-diskstats --proc-filesystems --proc-interrupts --proc-iomem --proc-ioports --proc-loadavg --proc-locks --proc-meminfo --proc-modules --proc-mtrr --proc-pagetypeinfo --proc-partitions --proc-slabinfo --proc-softirqs --proc-stat --proc-swaps --proc-uptime --proc-version --proc-vmallocinfo --proc-vmstat --proc-zoneinfo --proc-driver-rtc --proc-net-arp --proc-net-dev --proc-net-dev-mcast --proc-net-if-inet6 --proc-net-igmp --proc-net-igmp6 --proc-net-ipv6-route --proc-net-netlink --proc-net-netstat --proc-net-packet --proc-net-protocols --proc-net-route --proc-net-unix --proc-pid-fdinfo --proc-pid-io --proc-pid-maps --proc-pid-mountinfo --proc-pid-numa-maps --proc-pid-smaps --proc-pid-stat --proc-pid-statm --proc-pid-status --ps --route --rpm-qi --rsync --rsync-s --semver --sfdisk --shadow --ss --sshd-conf --stat --stat-s --sysctl --syslog --syslog-s --syslog-bsd --syslog-bsd-s --systemctl --systemctl-lj --systemctl-ls --systemctl-luf --systeminfo --time --timedatectl --timestamp --top --top-s --tracepath --traceroute --udevadm --ufw --ufw-appinfo --uname --update-alt-gs --update-alt-q --upower --uptime --url --vmstat --vmstat-s --w --wc --who --x509-cert --xml --xrandr --yaml --zipinfo)
jc_parsers=(--acpi --airport --airport-s --arp --asciitable --asciitable-m --blkid --cbt --cef --cef-s --chage --cksum --clf --clf-s --crontab --crontab-u --csv --csv-s --date --datetime-iso --df --dig --dir --dmidecode --dpkg-l --du --email-address --env --file --findmnt --finger --free --fstab --git-log --git-log-s --git-ls-remote --gpg --group --gshadow --hash --hashsum --hciconfig --history --hosts --id --ifconfig --ini --ini-dup --iostat --iostat-s --ip-address --iptables --iw-scan --iwconfig --jar-manifest --jobs --jwt --kv --last --ls --ls-s --lsblk --lsmod --lsof --lspci --lsusb --m3u --mdadm --mount --mpstat --mpstat-s --netstat --nmcli --ntpq --openvpn --os-prober --passwd --pci-ids --pgpass --pidstat --pidstat-s --ping --ping-s --pip-list --pip-show --plist --postconf --proc --proc-buddyinfo --proc-consoles --proc-cpuinfo --proc-crypto --proc-devices --proc-diskstats --proc-filesystems --proc-interrupts --proc-iomem --proc-ioports --proc-loadavg --proc-locks --proc-meminfo --proc-modules --proc-mtrr --proc-pagetypeinfo --proc-partitions --proc-slabinfo --proc-softirqs --proc-stat --proc-swaps --proc-uptime --proc-version --proc-vmallocinfo --proc-vmstat --proc-zoneinfo --proc-driver-rtc --proc-net-arp --proc-net-dev --proc-net-dev-mcast --proc-net-if-inet6 --proc-net-igmp --proc-net-igmp6 --proc-net-ipv6-route --proc-net-netlink --proc-net-netstat --proc-net-packet --proc-net-protocols --proc-net-route --proc-net-unix --proc-pid-fdinfo --proc-pid-io --proc-pid-maps --proc-pid-mountinfo --proc-pid-numa-maps --proc-pid-smaps --proc-pid-stat --proc-pid-statm --proc-pid-status --ps --route --rpm-qi --rsync --rsync-s --semver --sfdisk --shadow --ss --ssh-conf --sshd-conf --stat --stat-s --sysctl --syslog --syslog-s --syslog-bsd --syslog-bsd-s --systemctl --systemctl-lj --systemctl-ls --systemctl-luf --systeminfo --time --timedatectl --timestamp --toml --top --top-s --tracepath --traceroute --udevadm --ufw --ufw-appinfo --uname --update-alt-gs --update-alt-q --upower --uptime --url --ver --vmstat --vmstat-s --w --wc --who --x509-cert --xml --xrandr --yaml --zipinfo --zpool-iostat --zpool-status)
jc_parsers_describe=(
'--acpi:`acpi` command parser'
'--airport:`airport -I` command parser'
@@ -151,6 +153,7 @@ _jc() {
'--id:`id` command parser'
'--ifconfig:`ifconfig` command parser'
'--ini:INI file parser'
'--ini-dup:INI with duplicate key file parser'
'--iostat:`iostat` command parser'
'--iostat-s:`iostat` command streaming parser'
'--ip-address:IPv4 and IPv6 Address string parser'
@@ -249,7 +252,8 @@ _jc() {
'--sfdisk:`sfdisk` command parser'
'--shadow:`/etc/shadow` file parser'
'--ss:`ss` command parser'
'--sshd-conf:sshd config file and `sshd -T` command parser'
'--ssh-conf:`ssh` config file and `ssh -G` command parser'
'--sshd-conf:`sshd` config file and `sshd -T` command parser'
'--stat:`stat` command parser'
'--stat-s:`stat` command streaming parser'
'--sysctl:`sysctl` command parser'
@@ -265,6 +269,7 @@ _jc() {
'--time:`/usr/bin/time` command parser'
'--timedatectl:`timedatectl status` command parser'
'--timestamp:Unix Epoch Timestamp string parser'
'--toml:TOML file parser'
'--top:`top -b` command parser'
'--top-s:`top -b` command streaming parser'
'--tracepath:`tracepath` and `tracepath6` command parser'
@@ -278,6 +283,7 @@ _jc() {
'--upower:`upower` command parser'
'--uptime:`uptime` command parser'
'--url:URL string parser'
'--ver:Version string parser'
'--vmstat:`vmstat` command parser'
'--vmstat-s:`vmstat` command streaming parser'
'--w:`w` command parser'
@@ -288,6 +294,8 @@ _jc() {
'--xrandr:`xrandr` command parser'
'--yaml:YAML file parser'
'--zipinfo:`zipinfo` command parser'
'--zpool-iostat:`zpool iostat` command parser'
'--zpool-status:`zpool status` command parser'
)
jc_options=(--force-color -C --debug -d --monochrome -m --meta-out -M --pretty -p --quiet -q --raw -r --unbuffer -u --yaml-out -y)
jc_options_describe=(

View File

@@ -26,7 +26,7 @@ def parse(
data: Union[str, bytes, Iterable[str]],
quiet: bool = False,
raw: bool = False,
ignore_exceptions: bool = None,
ignore_exceptions: Optional[bool] = None,
**kwargs
) -> Union[JSONDictType, List[JSONDictType], Iterator[JSONDictType]]
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,13 +3,15 @@
# jc.parsers.ini
jc - JSON Convert `INI` file parser
jc - JSON Convert INI file parser
Parses standard `INI` files and files containing simple key/value pairs.
Parses standard INI files.
- Delimiter can be `=` or `:`. Missing values are supported.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If duplicate keys are found, only the last value will be used.
- If any section names have the same name as a top-level key, the top-level
key will be overwritten by the section data.
> Note: Values starting and ending with double or single quotation marks
> will have the marks removed. If you would like to keep the quotation
@@ -27,45 +29,47 @@ Usage (module):
Schema:
ini or key/value document converted to a dictionary - see the configparser
INI document converted to a dictionary - see the python configparser
standard library documentation for more details.
{
"key1": string,
"key2": string
"<key1>": string,
"<key2>": string,
"<section1>": {
"<key1>": string,
"<key2>": string
},
"<section2>": {
"<key1>": string,
"<key2>": string
}
}
Examples:
$ cat example.ini
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes
foo = fiz
bar = buz
[bitbucket.org]
User = hg
[section1]
fruit = apple
color = blue
[topsecret.server.com]
Port = 50022
ForwardX11 = no
[section2]
fruit = pear
color = green
$ cat example.ini | jc --ini -p
{
"bitbucket.org": {
"ServerAliveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "yes",
"User": "hg"
"foo": "fiz",
"bar": "buz",
"section1": {
"fruit": "apple",
"color": "blue"
},
"topsecret.server.com": {
"ServerAliveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "no",
"Port": "50022"
"section2": {
"fruit": "pear",
"color": "green"
}
}
@@ -87,9 +91,9 @@ Parameters:
Returns:
Dictionary representing the ini file
Dictionary representing the INI file.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 2.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

121
docs/parsers/ini_dup.md Normal file
View File

@@ -0,0 +1,121 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.ini_dup"></a>
# jc.parsers.ini\_dup
jc - JSON Convert INI with duplicate key file parser
Parses standard INI files and preserves duplicate values. All values are
contained in lists/arrays.
- Delimiter can be `=` or `:`. Missing values are supported.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If any section names have the same name as a top-level key, the top-level
key will be overwritten by the section data.
- If multi-line values are used, each line will be a separate item in the
value list. Blank lines in multi-line values are not supported.
> Note: Values starting and ending with double or single quotation marks
> will have the marks removed. If you would like to keep the quotation
> marks, use the `-r` command-line argument or the `raw=True` argument in
> `parse()`.
Usage (cli):
$ cat foo.ini | jc --ini
Usage (module):
import jc
result = jc.parse('ini', ini_file_output)
Schema:
INI document converted to a dictionary - see the python configparser
standard library documentation for more details.
{
"<key1>": [
string
],
"<key2>": [
string
],
"<section1>": {
"<key1>": [
string
],
"<key2>": [
string
]
}
}
Examples:
$ cat example.ini
foo = fiz
bar = buz
[section1]
fruit = apple
color = blue
color = red
[section2]
fruit = pear
fruit = peach
color = green
$ cat example.ini | jc --ini -p
{
"foo": [
"fiz"
],
"bar": [
"buz"
],
"section1": {
"fruit": [
"apple"
],
"color": [
"blue",
"red"
]
},
"section2": {
"fruit": [
"pear",
"peach"
],
"color": [
"green"
]
}
}
<a id="jc.parsers.ini_dup.parse"></a>
### parse
```python
def parse(data, raw=False, quiet=False)
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Dictionary representing the INI file.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -51,7 +51,6 @@ Schema:
}
]
Examples:
$ iwconfig | jc --iwconfig -p

View File

@@ -26,8 +26,8 @@ Usage (module):
Schema:
key/value document converted to a dictionary - see the configparser standard
library documentation for more details.
Key/Value document converted to a dictionary - see the python configparser
standard library documentation for more details.
{
"key1": string,
@@ -41,6 +41,7 @@ Examples:
name = John Doe
address=555 California Drive
age: 34
; comments can include # or ;
# delimiter can be = or :
# quoted values have quotation marks stripped by default
@@ -65,8 +66,6 @@ def parse(data, raw=False, quiet=False)
Main text parsing function
Note: this is just a wrapper for jc.parsers.ini
Parameters:
data: (string) text data to parse
@@ -75,9 +74,9 @@ Parameters:
Returns:
Dictionary representing the key/value file
Dictionary representing a Key/Value pair document.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 2.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

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

View File

@@ -25,7 +25,7 @@ Schema:
"filesystem": string,
"mount_point": string,
"type": string,
"access": [
"options": [
string
]
}
@@ -39,7 +39,7 @@ Example:
"filesystem": "sysfs",
"mount_point": "/sys",
"type": "sysfs",
"access": [
"options": [
"rw",
"nosuid",
"nodev",
@@ -51,7 +51,7 @@ Example:
"filesystem": "proc",
"mount_point": "/proc",
"type": "proc",
"access": [
"options": [
"rw",
"nosuid",
"nodev",
@@ -63,7 +63,7 @@ Example:
"filesystem": "udev",
"mount_point": "/dev",
"type": "devtmpfs",
"access": [
"options": [
"rw",
"nosuid",
"relatime",
@@ -96,6 +96,6 @@ Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, freebsd
Compatibility: linux, darwin, freebsd, aix
Version 1.7 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)

548
docs/parsers/ssh_conf.md Normal file
View File

@@ -0,0 +1,548 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.ssh_conf"></a>
# jc.parsers.ssh\_conf
jc - JSON Convert `ssh` configuration file and `ssh -G` command output parser
This parser will work with `ssh` configuration files or the output of
`ssh -G`. Any `Match` blocks in the `ssh` configuration file will be
ignored.
Usage (cli):
$ ssh -G hostname | jc --ssh-conf
or
$ jc ssh -G hostname
or
$ cat ~/.ssh/config | jc --ssh-conf
Usage (module):
import jc
result = jc.parse('ssh_conf', ssh_conf_output)
Schema:
[
{
"host": string,
"host_list": [
string
],
"addkeystoagent": string,
"addressfamily": string,
"batchmode": string,
"bindaddress": string,
"bindinterface": string,
"canonicaldomains": [
string
],
"canonicalizefallbacklocal": string,
"canonicalizehostname": string,
"canonicalizemaxdots": integer,
"canonicalizepermittedcnames": [
string
],
"casignaturealgorithms": [
string
],
"certificatefile": [
string
],
"checkhostip": string,
"ciphers": [
string
],
"clearallforwardings": string,
"compression": string,
"connectionattempts": integer,
"connecttimeout": integer,
"controlmaster": string,
"controlpath": string,
"controlpersist": string,
"dynamicforward": string,
"enableescapecommandline": string,
"enablesshkeysign": string,
"escapechar": string,
"exitonforwardfailure": string,
"fingerprinthash": string,
"forkafterauthentication": string,
"forwardagent": string,
"forwardx11": string,
"forwardx11timeout": integer,
"forwardx11trusted": string,
"gatewayports": string,
"globalknownhostsfile": [
string
],
"gssapiauthentication": string,
"gssapidelegatecredentials": string,
"hashknownhosts": string,
"hostbasedacceptedalgorithms": [
string
],
"hostbasedauthentication": string,
"hostkeyalgorithms": [
string
],
"hostkeyalias": string,
"hostname": string,
"identitiesonly": string,
"identityagent": string,
"identityfile": [
string
],
"ignoreunknown": string,
"include": [
string
],
"ipqos": [
string
],
"kbdinteractiveauthentication": string,
"kbdinteractivedevices": [
string
],
"kexalgorithms": [
string
],
"kexalgorithms_strategy": string,
"knownhostscommand": string,
"localcommand": string,
"localforward": [
string
],
"loglevel": string,
"logverbose": [
string
],
"macs": [
string
],
"macs_strategy": string,
"nohostauthenticationforlocalhost": string,
"numberofpasswordprompts": integer,
"passwordauthentication": string,
"permitlocalcommand": string,
"permitremoteopen": [
string
],
"pkcs11provider": string,
"port": integer,
"preferredauthentications": [
string
],
"protocol": integer,
"proxycommand": string,
"proxyjump": [
string
],
"proxyusefdpass": string,
"pubkeyacceptedalgorithms": [
string
],
"pubkeyacceptedalgorithms_strategy": string,
"pubkeyauthentication": string,
"rekeylimit": string,
"remotecommand": string,
"remoteforward": string,
"requesttty": string,
"requiredrsasize": integer,
"revokedhostkeys": string,
"securitykeyprovider": string,
"sendenv": [
string
],
"serveralivecountmax": integer,
"serveraliveinterval": integer,
"sessiontype": string,
"setenv": [
string
],
"stdinnull": string,
"streamlocalbindmask": string,
"streamlocalbindunlink": string,
"stricthostkeychecking": string,
"syslogfacility": string,
"tcpkeepalive": string,
"tunnel": string,
"tunneldevice": string,
"updatehostkeys": string,
"user": string,
"userknownhostsfile": [
string
],
"verifyhostkeydns": string,
"visualhostkey": string,
"xauthlocation": string
}
]
Examples:
$ ssh -G - | jc --ssh-conf -p
[
{
"user": "foo",
"hostname": "-",
"port": 22,
"addressfamily": "any",
"batchmode": "no",
"canonicalizefallbacklocal": "yes",
"canonicalizehostname": "false",
"checkhostip": "no",
"compression": "no",
"controlmaster": "false",
"enablesshkeysign": "no",
"clearallforwardings": "no",
"exitonforwardfailure": "no",
"fingerprinthash": "SHA256",
"forwardx11": "no",
"forwardx11trusted": "no",
"gatewayports": "no",
"gssapiauthentication": "no",
"gssapidelegatecredentials": "no",
"hashknownhosts": "no",
"hostbasedauthentication": "no",
"identitiesonly": "no",
"kbdinteractiveauthentication": "yes",
"nohostauthenticationforlocalhost": "no",
"passwordauthentication": "yes",
"permitlocalcommand": "no",
"proxyusefdpass": "no",
"pubkeyauthentication": "true",
"requesttty": "auto",
"sessiontype": "default",
"stdinnull": "no",
"forkafterauthentication": "no",
"streamlocalbindunlink": "no",
"stricthostkeychecking": "ask",
"tcpkeepalive": "yes",
"tunnel": "false",
"verifyhostkeydns": "false",
"visualhostkey": "no",
"updatehostkeys": "true",
"applemultipath": "no",
"canonicalizemaxdots": 1,
"connectionattempts": 1,
"forwardx11timeout": 1200,
"numberofpasswordprompts": 3,
"serveralivecountmax": 3,
"serveraliveinterval": 0,
"ciphers": [
"chacha20-poly1305@openssh.com",
"aes128-ctr",
"aes192-ctr",
"aes256-ctr",
"aes128-gcm@openssh.com",
"aes256-gcm@openssh.com"
],
"hostkeyalgorithms": [
"ssh-ed25519-cert-v01@openssh.com",
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
"ecdsa-sha2-nistp521-cert-v01@openssh.com",
"rsa-sha2-512-cert-v01@openssh.com",
"rsa-sha2-256-cert-v01@openssh.com",
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"rsa-sha2-512",
"rsa-sha2-256"
],
"hostbasedacceptedalgorithms": [
"ssh-ed25519-cert-v01@openssh.com",
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
"ecdsa-sha2-nistp521-cert-v01@openssh.com",
"rsa-sha2-512-cert-v01@openssh.com",
"rsa-sha2-256-cert-v01@openssh.com",
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"rsa-sha2-512",
"rsa-sha2-256"
],
"kexalgorithms": [
"sntrup761x25519-sha512@openssh.com",
"curve25519-sha256",
"curve25519-sha256@libssh.org",
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group16-sha512",
"diffie-hellman-group18-sha512",
"diffie-hellman-group14-sha256"
],
"casignaturealgorithms": [
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"rsa-sha2-512",
"rsa-sha2-256"
],
"loglevel": "INFO",
"macs": [
"umac-64-etm@openssh.com",
"umac-128-etm@openssh.com",
"hmac-sha2-256-etm@openssh.com",
"hmac-sha2-512-etm@openssh.com",
"hmac-sha1-etm@openssh.com",
"umac-64@openssh.com",
"umac-128@openssh.com",
"hmac-sha2-256",
"hmac-sha2-512",
"hmac-sha1"
],
"securitykeyprovider": "$SSH_SK_PROVIDER",
"pubkeyacceptedalgorithms": [
"ssh-ed25519-cert-v01@openssh.com",
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
"ecdsa-sha2-nistp521-cert-v01@openssh.com",
"rsa-sha2-512-cert-v01@openssh.com",
"rsa-sha2-256-cert-v01@openssh.com",
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"rsa-sha2-512",
"rsa-sha2-256"
],
"xauthlocation": "/usr/X11R6/bin/xauth",
"identityfile": [
"~/.ssh/id_rsa",
"~/.ssh/id_ecdsa",
"~/.ssh/id_ecdsa_sk",
"~/.ssh/id_ed25519",
"~/.ssh/id_ed25519_sk",
"~/.ssh/id_xmss",
"~/.ssh/id_dsa"
],
"canonicaldomains": [
"none"
],
"globalknownhostsfile": [
"/etc/ssh/ssh_known_hosts",
"/etc/ssh/ssh_known_hosts2"
],
"userknownhostsfile": [
"/Users/foo/.ssh/known_hosts",
"/Users/foo/.ssh/known_hosts2"
],
"sendenv": [
"LANG",
"LC_*"
],
"logverbose": [
"none"
],
"permitremoteopen": [
"any"
],
"addkeystoagent": "false",
"forwardagent": "no",
"connecttimeout": null,
"tunneldevice": "any:any",
"canonicalizepermittedcnames": [
"none"
],
"controlpersist": "no",
"escapechar": "~",
"ipqos": [
"af21",
"cs1"
],
"rekeylimit": "0 0",
"streamlocalbindmask": "0177",
"syslogfacility": "USER"
}
]
$ cat ~/.ssh/config | jc --ssh-conf -p
[
{
"host": "server1",
"host_list": [
"server1"
],
"hostname": "server1.cyberciti.biz",
"user": "nixcraft",
"port": 4242,
"identityfile": [
"/nfs/shared/users/nixcraft/keys/server1/id_rsa"
]
},
{
"host": "nas01",
"host_list": [
"nas01"
],
"hostname": "192.168.1.100",
"user": "root",
"identityfile": [
"~/.ssh/nas01.key"
]
},
{
"host": "aws.apache",
"host_list": [
"aws.apache"
],
"hostname": "1.2.3.4",
"user": "wwwdata",
"identityfile": [
"~/.ssh/aws.apache.key"
]
},
{
"host": "uk.gw.lan uk.lan",
"host_list": [
"uk.gw.lan",
"uk.lan"
],
"hostname": "192.168.0.251",
"user": "nixcraft",
"proxycommand": "ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null"
},
{
"host": "proxyus",
"host_list": [
"proxyus"
],
"hostname": "vps1.cyberciti.biz",
"user": "breakfree",
"identityfile": [
"~/.ssh/vps1.cyberciti.biz.key"
],
"localforward": [
"3128 127.0.0.1:3128"
]
},
{
"host": "*",
"host_list": [
"*"
],
"forwardagent": "no",
"forwardx11": "no",
"forwardx11trusted": "yes",
"user": "nixcraft",
"port": 22,
"protocol": 2,
"serveraliveinterval": 60,
"serveralivecountmax": 30
}
]
$ cat ~/.ssh/config | jc --ssh-conf -p -r
[
{
"host": "server1",
"host_list": [
"server1"
],
"hostname": "server1.cyberciti.biz",
"user": "nixcraft",
"port": "4242",
"identityfile": [
"/nfs/shared/users/nixcraft/keys/server1/id_rsa"
]
},
{
"host": "nas01",
"host_list": [
"nas01"
],
"hostname": "192.168.1.100",
"user": "root",
"identityfile": [
"~/.ssh/nas01.key"
]
},
{
"host": "aws.apache",
"host_list": [
"aws.apache"
],
"hostname": "1.2.3.4",
"user": "wwwdata",
"identityfile": [
"~/.ssh/aws.apache.key"
]
},
{
"host": "uk.gw.lan uk.lan",
"host_list": [
"uk.gw.lan",
"uk.lan"
],
"hostname": "192.168.0.251",
"user": "nixcraft",
"proxycommand": "ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null"
},
{
"host": "proxyus",
"host_list": [
"proxyus"
],
"hostname": "vps1.cyberciti.biz",
"user": "breakfree",
"identityfile": [
"~/.ssh/vps1.cyberciti.biz.key"
],
"localforward": [
"3128 127.0.0.1:3128"
]
},
{
"host": "*",
"host_list": [
"*"
],
"forwardagent": "no",
"forwardx11": "no",
"forwardx11trusted": "yes",
"user": "nixcraft",
"port": "22",
"protocol": "2",
"serveraliveinterval": "60",
"serveralivecountmax": "30"
}
]
<a id="jc.parsers.ssh_conf.parse"></a>
### parse
```python
def parse(data: str,
raw: bool = False,
quiet: bool = False) -> List[JSONDictType]
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -3,7 +3,7 @@
# jc.parsers.sshd\_conf
jc - JSON Convert sshd configuration file and `sshd -T` command output parser
jc - JSON Convert `sshd` configuration file and `sshd -T` command output parser
This parser will work with `sshd` configuration files or the output of
`sshd -T`. Any `Match` blocks in the `sshd` configuration file will be
@@ -504,4 +504,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)

81
docs/parsers/toml.md Normal file
View File

@@ -0,0 +1,81 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.toml"></a>
# jc.parsers.toml
jc - JSON Convert TOML file parser
Usage (cli):
$ cat file.toml | jc --toml
Usage (module):
import jc
result = jc.parse('toml', toml_file_output)
Schema:
TOML Document converted to a Dictionary.
See https://toml.io/en/ for details.
{
"key1": string/int/float/boolean/null/array/object,
"key2": string/int/float/boolean/null/array/object
}
Examples:
$ cat file.toml
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
$ cat file.toml | jc --toml -p
{
"title": "TOML Example",
"owner": {
"name": "Tom Preston-Werner",
"dob": 296667120,
"dob_iso": "1979-05-27T07:32:00-08:00"
},
"database": {
"enabled": true,
"ports": [
8000,
8001,
8002
]
}
}
<a id="jc.parsers.toml.parse"></a>
### parse
```python
def parse(data: str, raw: bool = False, quiet: bool = False) -> JSONDictType
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Dictionary. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -42,8 +42,7 @@ Parameters:
underscore '_'. You should also ensure headers are
lowercase by using .lower().
Also, ensure there are no blank lines (list items)
in the data.
Also, ensure there are no blank rows in the data.
Returns:

View File

@@ -8,6 +8,7 @@ jc - JSON Convert `xrandr` command output parser
Usage (cli):
$ xrandr | jc --xrandr
$ xrandr --properties | jc --xrandr
or
@@ -49,13 +50,17 @@ Schema:
"is_connected": boolean,
"is_primary": boolean,
"device_name": string,
"model_name": string,
"product_id" string,
"serial_number": string,
"resolution_width": integer,
"resolution_height": integer,
"offset_width": integer,
"offset_height": integer,
"dimension_width": integer,
"dimension_height": integer,
"rotation": string
"rotation": string,
"reflection": string
}
],
"unassociated_devices": [
@@ -132,7 +137,71 @@ Examples:
"offset_height": 0,
"dimension_width": 310,
"dimension_height": 170,
"rotation": "normal"
"rotation": "normal",
"reflection": "normal"
}
}
],
"unassociated_devices": []
}
$ xrandr --properties | jc --xrandr -p
{
"screens": [
{
"screen_number": 0,
"minimum_width": 8,
"minimum_height": 8,
"current_width": 1920,
"current_height": 1080,
"maximum_width": 32767,
"maximum_height": 32767,
"associated_device": {
"associated_modes": [
{
"resolution_width": 1920,
"resolution_height": 1080,
"is_high_resolution": false,
"frequencies": [
{
"frequency": 60.03,
"is_current": true,
"is_preferred": true
},
{
"frequency": 59.93,
"is_current": false,
"is_preferred": false
}
]
},
{
"resolution_width": 1680,
"resolution_height": 1050,
"is_high_resolution": false,
"frequencies": [
{
"frequency": 59.88,
"is_current": false,
"is_preferred": false
}
]
}
],
"is_connected": true,
"is_primary": true,
"device_name": "eDP1",
"model_name": "ASUS VW193S",
"product_id": "54297",
"serial_number": "78L8021107",
"resolution_width": 1920,
"resolution_height": 1080,
"offset_width": 0,
"offset_height": 0,
"dimension_width": 310,
"dimension_height": 170,
"rotation": "normal",
"reflection": "normal"
}
}
],
@@ -162,4 +231,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, aix, freebsd
Version 1.1 by Kevin Lyter (lyter_git at sent.com)
Version 1.2 by Kevin Lyter (lyter_git at sent.com)

View File

@@ -18,8 +18,8 @@ Usage (module):
Schema:
YAML Document converted to a Dictionary
See https://pypi.org/project/ruamel.yaml for details
YAML Document converted to a Dictionary.
See https://pypi.org/project/ruamel.yaml for details.
[
{
@@ -30,7 +30,7 @@ Schema:
Examples:
$ cat istio-mtls-permissive.yaml
$ cat file.yaml
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
@@ -51,7 +51,7 @@ Examples:
tls:
mode: ISTIO_MUTUAL
$ cat istio-mtls-permissive.yaml | jc --yaml -p
$ cat file.yaml | jc --yaml -p
[
{
"apiVersion": "authentication.istio.io/v1alpha1",

View File

@@ -99,4 +99,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin
Version 1.1 by Matt J (https://github.com/listuser)
Version 1.2 by Matt J (https://github.com/listuser)

View File

@@ -0,0 +1,125 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.zpool_iostat"></a>
# jc.parsers.zpool\_iostat
jc - JSON Convert `zpool iostat` command output parser
Supports with or without the `-v` flag.
Usage (cli):
$ zpool iostat | jc --zpool-iostat
or
$ jc zpool iostat
Usage (module):
import jc
result = jc.parse('zpool_iostat', zpool_iostat_command_output)
Schema:
[
{
"pool": string,
"parent": string,
"cap_alloc": float,
"cap_alloc_unit": string,
"cap_free": float,
"cap_free_unit": string,
"ops_read": integer,
"ops_write": integer,
"bw_read": float,
"bw_read_unit": string,
"bw_write": float,
"bw_write_unit": string
}
]
Examples:
$ zpool iostat -v | jc --zpool-iostat -p
[
{
"pool": "zhgstera6",
"cap_alloc": 2.89,
"cap_free": 2.2,
"ops_read": 0,
"ops_write": 2,
"bw_read": 349.0,
"bw_write": 448.0,
"cap_alloc_unit": "T",
"cap_free_unit": "T",
"bw_read_unit": "K",
"bw_write_unit": "K"
},
{
"pool": "726060ALE614-K8JAPRGN:10",
"parent": "zhgstera6",
"cap_alloc": 2.89,
"cap_free": 2.2,
"ops_read": 0,
"ops_write": 2,
"bw_read": 349.0,
"bw_write": 448.0,
"cap_alloc_unit": "T",
"cap_free_unit": "T",
"bw_read_unit": "K",
"bw_write_unit": "K"
},
...
]
$ zpool iostat -v | jc --zpool-iostat -p -r
[
{
"pool": "zhgstera6",
"cap_alloc": "2.89T",
"cap_free": "2.20T",
"ops_read": "0",
"ops_write": "2",
"bw_read": "349K",
"bw_write": "448K"
},
{
"pool": "726060ALE614-K8JAPRGN:10",
"parent": "zhgstera6",
"cap_alloc": "2.89T",
"cap_free": "2.20T",
"ops_read": "0",
"ops_write": "2",
"bw_read": "349K",
"bw_write": "448K"
},
...
]
<a id="jc.parsers.zpool_iostat.parse"></a>
### parse
```python
def parse(data: str,
raw: bool = False,
quiet: bool = False) -> List[JSONDictType]
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@@ -0,0 +1,163 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.zpool_status"></a>
# jc.parsers.zpool\_status
jc - JSON Convert `zpool status` command output parser
Works with or without the `-v` option.
Usage (cli):
$ zpool status | jc --zpool-status
or
$ jc zpool status
Usage (module):
import jc
result = jc.parse('zpool_status', zpool_status_command_output)
Schema:
[
{
"pool": string,
"state": string,
"status": string,
"action": string,
"see": string,
"scan": string,
"scrub": string,
"config": [
{
"name": string,
"state": string,
"read": integer,
"write": integer,
"checksum": integer,
"errors": string,
}
],
"errors": string
}
]
Examples:
$ zpool status -v | jc --zpool-status -p
[
{
"pool": "tank",
"state": "DEGRADED",
"status": "One or more devices could not be opened. Suffic...",
"action": "Attach the missing device and online it using 'zpool...",
"see": "http://www.sun.com/msg/ZFS-8000-2Q",
"scrub": "none requested",
"config": [
{
"name": "tank",
"state": "DEGRADED",
"read": 0,
"write": 0,
"checksum": 0
},
{
"name": "mirror-0",
"state": "DEGRADED",
"read": 0,
"write": 0,
"checksum": 0
},
{
"name": "c1t0d0",
"state": "ONLINE",
"read": 0,
"write": 0,
"checksum": 0
},
{
"name": "c1t1d0",
"state": "UNAVAIL",
"read": 0,
"write": 0,
"checksum": 0,
"errors": "cannot open"
}
],
"errors": "No known data errors"
}
]
$ zpool status -v | jc --zpool-status -p -r
[
{
"pool": "tank",
"state": "DEGRADED",
"status": "One or more devices could not be opened. Sufficient...",
"action": "Attach the missing device and online it using 'zpool...",
"see": "http://www.sun.com/msg/ZFS-8000-2Q",
"scrub": "none requested",
"config": [
{
"name": "tank",
"state": "DEGRADED",
"read": "0",
"write": "0",
"checksum": "0"
},
{
"name": "mirror-0",
"state": "DEGRADED",
"read": "0",
"write": "0",
"checksum": "0"
},
{
"name": "c1t0d0",
"state": "ONLINE",
"read": "0",
"write": "0",
"checksum": "0"
},
{
"name": "c1t1d0",
"state": "UNAVAIL",
"read": "0",
"write": "0",
"checksum": "0",
"errors": "cannot open"
}
],
"errors": "No known data errors"
}
]
<a id="jc.parsers.zpool_status.parse"></a>
### parse
```python
def parse(data: str,
raw: bool = False,
quiet: bool = False) -> List[JSONDictType]
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux, darwin, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

200
jc/cli.py
View File

@@ -5,11 +5,13 @@ JC cli module
import io
import sys
import os
import re
from itertools import islice
from datetime import datetime, timezone
import textwrap
import shlex
import subprocess
from typing import List, Dict, Union, Optional, TextIO
from typing import List, Dict, Iterable, Union, Optional, TextIO
from types import ModuleType
from .lib import (
__version__, parser_info, all_parser_info, parsers, _get_parser, _parser_is_streaming,
@@ -40,6 +42,10 @@ except Exception:
JC_CLEAN_EXIT: int = 0
JC_ERROR_EXIT: int = 100
MAX_EXIT: int = 255
SLICER_PATTERN: str = r'-?[0-9]*\:-?[0-9]*$'
SLICER_RE = re.compile(SLICER_PATTERN)
NEWLINES_PATTERN: str = r'(\r\n|\r|\n)'
NEWLINES_RE = re.compile(NEWLINES_PATTERN)
class info():
@@ -48,7 +54,7 @@ class info():
author: str = 'Kelly Brazil'
author_email: str = 'kellyjonbrazil@gmail.com'
website: str = 'https://github.com/kellyjonbrazil/jc'
copyright: str = '© 2019-2022 Kelly Brazil'
copyright: str = '© 2019-2023 Kelly Brazil'
license: str = 'MIT License'
@@ -69,11 +75,11 @@ class JcCli():
'help_me', 'pretty', 'quiet', 'ignore_exceptions', 'raw', 'meta_out', 'unbuffer',
'version_info', 'yaml_output', 'bash_comp', 'zsh_comp', 'magic_found_parser',
'magic_options', 'magic_run_command', 'magic_run_command_str', 'magic_stdout',
'magic_stderr', 'magic_returncode'
'magic_stderr', 'magic_returncode', 'slice_str', 'slice_start', 'slice_end'
)
def __init__(self) -> None:
self.data_in: Optional[Union[str, bytes, TextIO]] = None
self.data_in: Optional[Union[str, bytes, TextIO, Iterable[str]]] = None
self.data_out: Optional[Union[List[JSONDictType], JSONDictType]] = None
self.options: List[str] = []
self.args: List[str] = []
@@ -89,6 +95,11 @@ class JcCli():
self.json_indent: Optional[int] = None
self.run_timestamp: Optional[datetime] = None
# slicer
self.slice_str: str = ''
self.slice_start: Optional[int] = None
self.slice_end: Optional[int] = None
# cli options
self.about: bool = False
self.debug: bool = False
@@ -432,6 +443,17 @@ class JcCli():
self.magic_options = []
return
# slicer found
if ':' in arg:
if SLICER_RE.match(arg):
self.slice_str = arg
args_given.pop(0)
continue
else:
utils.warning_message(['Invalid slice syntax.'])
args_given.pop(0)
continue
# option found - populate option list
if arg.startswith('-'):
self.magic_options.extend(args_given.pop(0)[1:])
@@ -574,59 +596,6 @@ class JcCli():
utils.error_message(['Missing piped data. Use "jc -h" for help.'])
self.exit_error()
def streaming_parse_and_print(self) -> None:
"""only supports UTF-8 string data for now"""
self.data_in = sys.stdin
if self.parser_module:
result = self.parser_module.parse(
self.data_in,
raw=self.raw,
quiet=self.quiet,
ignore_exceptions=self.ignore_exceptions
)
for line in result:
self.data_out = line
if self.meta_out:
self.run_timestamp = datetime.now(timezone.utc)
self.add_metadata_to_output()
self.safe_print_out()
def standard_parse_and_print(self) -> None:
"""supports binary and UTF-8 string data"""
self.data_in = self.magic_stdout or sys.stdin.buffer.read()
# convert to UTF-8, if possible. Otherwise, leave as bytes
try:
if isinstance(self.data_in, bytes):
self.data_in = self.data_in.decode('utf-8')
except UnicodeDecodeError:
pass
if self.parser_module:
self.data_out = self.parser_module.parse(
self.data_in,
raw=self.raw,
quiet=self.quiet
)
if self.meta_out:
self.run_timestamp = datetime.now(timezone.utc)
self.add_metadata_to_output()
self.safe_print_out()
def exit_clean(self) -> None:
exit_code: int = self.magic_returncode + JC_CLEAN_EXIT
exit_code = min(exit_code, MAX_EXIT)
sys.exit(exit_code)
def exit_error(self) -> None:
exit_code: int = self.magic_returncode + JC_ERROR_EXIT
exit_code = min(exit_code, MAX_EXIT)
sys.exit(exit_code)
def add_metadata_to_output(self) -> None:
"""
This function mutates data_out in place. If the _jc_meta field
@@ -641,7 +610,9 @@ class JcCli():
if self.run_timestamp:
meta_obj: JSONDictType = {
'parser': self.parser_name,
'timestamp': self.run_timestamp.timestamp()
'timestamp': self.run_timestamp.timestamp(),
'slice_start': self.slice_start,
'slice_end': self.slice_end
}
if self.magic_run_command:
@@ -669,6 +640,116 @@ class JcCli():
utils.error_message(['Parser returned an unsupported object type.'])
self.exit_error()
@staticmethod
def lazy_splitlines(text: str) -> Iterable[str]:
start = 0
for m in NEWLINES_RE.finditer(text):
begin, end = m.span()
if begin != start:
yield text[start:begin]
start = end
if text[start:]:
yield text[start:]
def slicer(self) -> None:
"""Slice input data lazily, if possible. Updates self.data_in"""
if self.slice_str:
slice_start_str, slice_end_str = self.slice_str.split(':', maxsplit=1)
if slice_start_str:
self.slice_start = int(slice_start_str)
if slice_end_str:
self.slice_end = int(slice_end_str)
if not self.slice_start is None or not self.slice_end is None:
# standard parsers UTF-8 input
if isinstance(self.data_in, str):
data_in_iter = self.lazy_splitlines(self.data_in)
# positive slices
if (self.slice_start is None or self.slice_start >= 0) \
and (self.slice_end is None or self.slice_end >= 0):
self.data_in = '\n'.join(islice(data_in_iter, self.slice_start, self.slice_end))
# negative slices found (non-lazy, uses more memory)
else:
self.data_in = '\n'.join(list(data_in_iter)[self.slice_start:self.slice_end])
# standard parsers bytes input
elif isinstance(self.data_in, bytes):
utils.warning_message(['Cannot slice bytes data.'])
# streaming parsers UTF-8 input
else:
# positive slices
if (self.slice_start is None or self.slice_start >= 0) \
and (self.slice_end is None or self.slice_end >= 0) \
and self.data_in:
self.data_in = islice(self.data_in, self.slice_start, self.slice_end)
# negative slices found (non-lazy, uses more memory)
elif self.data_in:
self.data_in = list(self.data_in)[self.slice_start:self.slice_end]
def streaming_parse_and_print(self) -> None:
"""only supports UTF-8 string data for now"""
self.data_in = sys.stdin
self.slicer()
if self.parser_module:
result = self.parser_module.parse(
self.data_in,
raw=self.raw,
quiet=self.quiet,
ignore_exceptions=self.ignore_exceptions
)
for line in result:
self.data_out = line
if self.meta_out:
self.run_timestamp = datetime.now(timezone.utc)
self.add_metadata_to_output()
self.safe_print_out()
def standard_parse_and_print(self) -> None:
"""supports binary and UTF-8 string data"""
self.data_in = self.magic_stdout or sys.stdin.buffer.read()
# convert to UTF-8, if possible. Otherwise, leave as bytes
try:
if isinstance(self.data_in, bytes):
self.data_in = self.data_in.decode('utf-8')
except UnicodeDecodeError:
pass
self.slicer()
if self.parser_module:
self.data_out = self.parser_module.parse(
self.data_in,
raw=self.raw,
quiet=self.quiet
)
if self.meta_out:
self.run_timestamp = datetime.now(timezone.utc)
self.add_metadata_to_output()
self.safe_print_out()
def exit_clean(self) -> None:
exit_code: int = self.magic_returncode + JC_CLEAN_EXIT
exit_code = min(exit_code, MAX_EXIT)
sys.exit(exit_code)
def exit_error(self) -> None:
exit_code: int = self.magic_returncode + JC_ERROR_EXIT
exit_code = min(exit_code, MAX_EXIT)
sys.exit(exit_code)
def _run(self) -> None:
# enable colors for Windows cmd.exe terminal
if sys.platform.startswith('win32'):
@@ -684,6 +765,9 @@ class JcCli():
# find options if magic_parser did not find a command
if not self.magic_found_parser:
for opt in self.args:
if SLICER_RE.match(opt):
self.slice_str = opt
if opt in long_options_map:
self.options.extend(long_options_map[opt][0])

View File

@@ -63,17 +63,17 @@ Usage:
Standard syntax:
COMMAND | jc [OPTIONS] PARSER
COMMAND | jc [SLICE] [OPTIONS] PARSER
cat FILE | jc [OPTIONS] PARSER
cat FILE | jc [SLICE] [OPTIONS] PARSER
echo STRING | jc [OPTIONS] PARSER
echo STRING | jc [SLICE] [OPTIONS] PARSER
Magic syntax:
jc [OPTIONS] COMMAND
jc [SLICE] [OPTIONS] COMMAND
jc [OPTIONS] /proc/<path-to-procfile>
jc [SLICE] [OPTIONS] /proc/<path-to-procfile>
Parsers:
'''
@@ -88,6 +88,9 @@ Examples:
$ jc --pretty dig www.google.com
$ jc --pretty /proc/meminfo
Line Slicing:
$ cat file.csv | jc :101 --csv # parse first 100 lines
Parser Documentation:
$ jc --help --dig

View File

@@ -3,13 +3,13 @@ import sys
import os
import re
import importlib
from typing import List, Iterable, Union, Iterator
from typing import List, Iterable, Optional, Union, Iterator
from types import ModuleType
from .jc_types import ParserInfoType, JSONDictType
from jc import appdirs
__version__ = '1.22.4'
__version__ = '1.23.0'
parsers: List[str] = [
'acpi',
@@ -59,6 +59,7 @@ parsers: List[str] = [
'id',
'ifconfig',
'ini',
'ini-dup',
'iostat',
'iostat-s',
'ip-address',
@@ -158,6 +159,7 @@ parsers: List[str] = [
'sfdisk',
'shadow',
'ss',
'ssh-conf',
'sshd-conf',
'stat',
'stat-s',
@@ -174,6 +176,7 @@ parsers: List[str] = [
'time',
'timedatectl',
'timestamp',
'toml',
'top',
'top-s',
'tracepath',
@@ -187,6 +190,7 @@ parsers: List[str] = [
'upower',
'uptime',
'url',
'ver',
'vmstat',
'vmstat-s',
'w',
@@ -196,7 +200,9 @@ parsers: List[str] = [
'xml',
'xrandr',
'yaml',
'zipinfo'
'zipinfo',
'zpool-iostat',
'zpool-status'
]
def _cliname_to_modname(parser_cli_name: str) -> str:
@@ -277,7 +283,7 @@ def parse(
data: Union[str, bytes, Iterable[str]],
quiet: bool = False,
raw: bool = False,
ignore_exceptions: bool = None,
ignore_exceptions: Optional[bool] = None,
**kwargs
) -> Union[JSONDictType, List[JSONDictType], Iterator[JSONDictType]]:
"""

View File

@@ -227,7 +227,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.4'
version = '1.5'
description = '`acpi` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -336,7 +336,9 @@ def parse(data, raw=False, quiet=False):
if 'Charging' in line or 'Discharging' in line or 'Full' in line:
output_line['state'] = line.split()[2][:-1]
output_line['charge_percent'] = line.split()[3].rstrip('%,')
if 'rate information unavailable' not in line:
if 'will never fully discharge' in line:
pass
elif 'rate information unavailable' not in line:
if 'Charging' in line:
output_line['until_charged'] = line.split()[4]
if 'Discharging' in line:

View File

@@ -119,7 +119,7 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.11'
version = '1.12'
description = '`arp` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -222,14 +222,26 @@ def parse(
else:
for line in cleandata:
splitline = line.split()
if '<incomplete>' not in splitline:
# Ignore AIX bucket information
if 'bucket:' in splitline[0]:
continue
elif 'There' in splitline[0] and 'are' in splitline[1]:
continue
# AIX uses (incomplete)
elif '<incomplete>' not in splitline and '(incomplete)' not in splitline:
output_line = {
'name': splitline[0],
'address': splitline[1].lstrip('(').rstrip(')'),
'hwtype': splitline[4].lstrip('[').rstrip(']'),
'hwaddress': splitline[3],
'iface': splitline[6],
}
# Handle permanence and ignore interface in AIX
if 'permanent' in splitline:
output_line['permanent'] = True
elif 'in' not in splitline[6]: # AIX doesn't show interface
output_line['iface'] = splitline[6]
else:
output_line = {
@@ -237,8 +249,10 @@ def parse(
'address': splitline[1].lstrip('(').rstrip(')'),
'hwtype': None,
'hwaddress': None,
'iface': splitline[5],
}
# AIX doesn't show interface
if len(splitline) >= 5:
output_line['iface'] = splitline[5]
raw_output.append(output_line)

View File

@@ -174,7 +174,7 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.6'
version = '1.7'
description = '`crontab` command and file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -273,6 +273,9 @@ def parse(data, raw=False, quiet=False):
raw_output['schedule'] = cron_list
# Add shortcut entries back in
if 'schedule' not in raw_output:
raw_output['schedule'] = []
for item in shortcut_list:
raw_output['schedule'].append(item)

View File

@@ -171,7 +171,7 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.7'
version = '1.8'
description = '`crontab` file parser with user support'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -271,6 +271,9 @@ def parse(data, raw=False, quiet=False):
raw_output['schedule'] = cron_list
# Add shortcut entries back in
if 'schedule' not in raw_output:
raw_output['schedule'] = []
for item in shortcut_list:
raw_output['schedule'].append(item)

View File

@@ -219,7 +219,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '2.2'
version = '2.3'
description = '`ifconfig` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -425,18 +425,18 @@ def parse(
# Linux syntax
re_linux_interface = re.compile(r'''
(?P<name>[a-zA-Z0-9:._-]+)\s+
Link encap:(?P<type>\S+\s?\S+)
Link\sencap:(?P<type>\S+\s?\S+)
(\s+HWaddr\s+\b(?P<mac_addr>[0-9A-Fa-f:?]+))?
''', re.IGNORECASE | re.VERBOSE
)
re_linux_ipv4 = re.compile(r'''
inet addr:(?P<address>(?:[0-9]{1,3}\.){3}[0-9]{1,3})(\s+
inet\saddr:(?P<address>(?:[0-9]{1,3}\.){3}[0-9]{1,3})(\s+
Bcast:(?P<broadcast>(?:[0-9]{1,3}\.){3}[0-9]{1,3}))?\s+
Mask:(?P<mask>(?:[0-9]{1,3}\.){3}[0-9]{1,3})
''', re.IGNORECASE | re.VERBOSE
)
re_linux_ipv6 = re.compile(r'''
inet6 addr:\s+(?P<address>\S+)/
inet6\saddr:\s+(?P<address>\S+)/
(?P<mask>[0-9]+)\s+
Scope:(?P<scope>Link|Host)
''', re.IGNORECASE | re.VERBOSE
@@ -448,7 +448,7 @@ def parse(
''', re.IGNORECASE | re.VERBOSE
)
re_linux_rx = re.compile(r'''
RX packets:(?P<rx_packets>[0-9]+)\s+
RX\spackets:(?P<rx_packets>[0-9]+)\s+
errors:(?P<rx_errors>[0-9]+)\s+
dropped:(?P<rx_dropped>[0-9]+)\s+
overruns:(?P<rx_overruns>[0-9]+)\s+
@@ -456,7 +456,7 @@ def parse(
''', re.IGNORECASE | re.VERBOSE
)
re_linux_tx = re.compile(r'''
TX packets:(?P<tx_packets>[0-9]+)\s+
TX\spackets:(?P<tx_packets>[0-9]+)\s+
errors:(?P<tx_errors>[0-9]+)\s+
dropped:(?P<tx_dropped>[0-9]+)\s+
overruns:(?P<tx_overruns>[0-9]+)\s+
@@ -464,8 +464,8 @@ def parse(
''', re.IGNORECASE | re.VERBOSE
)
re_linux_bytes = re.compile(r'''
\W+RX bytes:(?P<rx_bytes>\d+)\s+\(.*\)\s+
TX bytes:(?P<tx_bytes>\d+)\s+\(.*\)
\W+RX\sbytes:(?P<rx_bytes>\d+)\s+\(.*\)\s+
TX\sbytes:(?P<tx_bytes>\d+)\s+\(.*\)
''', re.IGNORECASE | re.VERBOSE
)
re_linux_tx_stats = re.compile(r'''

View File

@@ -1,10 +1,12 @@
"""jc - JSON Convert `INI` file parser
"""jc - JSON Convert INI file parser
Parses standard `INI` files and files containing simple key/value pairs.
Parses standard INI files.
- Delimiter can be `=` or `:`. Missing values are supported.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If duplicate keys are found, only the last value will be used.
- If any section names have the same name as a top-level key, the top-level
key will be overwritten by the section data.
> Note: Values starting and ending with double or single quotation marks
> will have the marks removed. If you would like to keep the quotation
@@ -22,59 +24,62 @@ Usage (module):
Schema:
ini or key/value document converted to a dictionary - see the configparser
INI document converted to a dictionary - see the python configparser
standard library documentation for more details.
{
"key1": string,
"key2": string
"<key1>": string,
"<key2>": string,
"<section1>": {
"<key1>": string,
"<key2>": string
},
"<section2>": {
"<key1>": string,
"<key2>": string
}
}
Examples:
$ cat example.ini
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes
foo = fiz
bar = buz
[bitbucket.org]
User = hg
[section1]
fruit = apple
color = blue
[topsecret.server.com]
Port = 50022
ForwardX11 = no
[section2]
fruit = pear
color = green
$ cat example.ini | jc --ini -p
{
"bitbucket.org": {
"ServerAliveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "yes",
"User": "hg"
"foo": "fiz",
"bar": "buz",
"section1": {
"fruit": "apple",
"color": "blue"
},
"topsecret.server.com": {
"ServerAliveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "no",
"Port": "50022"
"section2": {
"fruit": "pear",
"color": "green"
}
}
"""
import jc.utils
import configparser
import uuid
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.8'
version = '2.0'
description = 'INI file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
details = 'Using configparser from the standard library'
details = 'Using configparser from the python standard library'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['standard', 'file', 'string']
@@ -82,6 +87,19 @@ class info():
__version__ = info.version
def _remove_quotes(value):
if value is None:
value = ''
elif value.startswith('"') and value.endswith('"'):
value = value[1:-1]
elif value.startswith("'") and value.endswith("'"):
value = value[1:-1]
return value
def _process(proc_data):
"""
Final processing to conform to the schema.
@@ -92,32 +110,16 @@ def _process(proc_data):
Returns:
Dictionary representing an ini or simple key/value pair document.
Dictionary representing the INI file.
"""
# remove quotation marks from beginning and end of values
for heading in proc_data:
# standard ini files with headers
if isinstance(proc_data[heading], dict):
for key, value in proc_data[heading].items():
if value is not None and value.startswith('"') and value.endswith('"'):
proc_data[heading][key] = value.lstrip('"').rstrip('"')
for k, v in proc_data.items():
if isinstance(v, dict):
for key, value in v.items():
v[key] = _remove_quotes(value)
continue
elif value is not None and value.startswith("'") and value.endswith("'"):
proc_data[heading][key] = value.lstrip("'").rstrip("'")
elif value is None:
proc_data[heading][key] = ''
# simple key/value files with no headers
else:
if proc_data[heading] is not None and proc_data[heading].startswith('"') and proc_data[heading].endswith('"'):
proc_data[heading] = proc_data[heading].lstrip('"').rstrip('"')
elif proc_data[heading] is not None and proc_data[heading].startswith("'") and proc_data[heading].endswith("'"):
proc_data[heading] = proc_data[heading].lstrip("'").rstrip("'")
elif proc_data[heading] is None:
proc_data[heading] = ''
proc_data[k] = _remove_quotes(v)
return proc_data
@@ -134,7 +136,7 @@ def parse(data, raw=False, quiet=False):
Returns:
Dictionary representing the ini file
Dictionary representing the INI file.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
@@ -143,25 +145,36 @@ def parse(data, raw=False, quiet=False):
if jc.utils.has_data(data):
ini = configparser.ConfigParser(allow_no_value=True,
interpolation=None,
strict=False)
ini_parser = configparser.ConfigParser(
allow_no_value=True,
interpolation=None,
default_section=None,
strict=False
)
# don't convert keys to lower-case:
ini.optionxform = lambda option: option
ini_parser.optionxform = lambda option: option
try:
ini.read_string(data)
raw_output = {s: dict(ini.items(s)) for s in ini.sections()}
ini_parser.read_string(data)
raw_output = {s: dict(ini_parser.items(s)) for s in ini_parser.sections()}
except configparser.MissingSectionHeaderError:
data = '[data]\n' + data
ini.read_string(data)
output_dict = {s: dict(ini.items(s)) for s in ini.sections()}
for key, value in output_dict['data'].items():
raw_output[key] = value
# find a top-level section name that will not collide with any existing ones
while True:
my_uuid = str(uuid.uuid4())
if my_uuid not in data:
break
data = f'[{my_uuid}]\n' + data
ini_parser.read_string(data)
temp_dict = {s: dict(ini_parser.items(s)) for s in ini_parser.sections()}
# move items under fake top-level sections to the root
raw_output = temp_dict.pop(my_uuid)
# get the rest of the sections
raw_output.update(temp_dict)
return raw_output if raw else _process(raw_output)
if raw:
return raw_output
else:
return _process(raw_output)

225
jc/parsers/ini_dup.py Normal file
View File

@@ -0,0 +1,225 @@
"""jc - JSON Convert INI with duplicate key file parser
Parses standard INI files and preserves duplicate values. All values are
contained in lists/arrays.
- Delimiter can be `=` or `:`. Missing values are supported.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If any section names have the same name as a top-level key, the top-level
key will be overwritten by the section data.
- If multi-line values are used, each line will be a separate item in the
value list. Blank lines in multi-line values are not supported.
> Note: Values starting and ending with double or single quotation marks
> will have the marks removed. If you would like to keep the quotation
> marks, use the `-r` command-line argument or the `raw=True` argument in
> `parse()`.
Usage (cli):
$ cat foo.ini | jc --ini
Usage (module):
import jc
result = jc.parse('ini', ini_file_output)
Schema:
INI document converted to a dictionary - see the python configparser
standard library documentation for more details.
{
"<key1>": [
string
],
"<key2>": [
string
],
"<section1>": {
"<key1>": [
string
],
"<key2>": [
string
]
}
}
Examples:
$ cat example.ini
foo = fiz
bar = buz
[section1]
fruit = apple
color = blue
color = red
[section2]
fruit = pear
fruit = peach
color = green
$ cat example.ini | jc --ini -p
{
"foo": [
"fiz"
],
"bar": [
"buz"
],
"section1": {
"fruit": [
"apple"
],
"color": [
"blue",
"red"
]
},
"section2": {
"fruit": [
"pear",
"peach"
],
"color": [
"green"
]
}
}
"""
import jc.utils
import configparser
import uuid
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = 'INI with duplicate key file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
details = 'Using configparser from the python standard library'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['standard', 'file', 'string']
__version__ = info.version
class MultiDict(dict):
# https://stackoverflow.com/a/38286559/12303989
def __setitem__(self, key, value):
if key in self:
if isinstance(value, list):
self[key].extend(value)
elif isinstance(value, str):
if len(self[key])>1:
return
else:
super().__setitem__(key, value)
def _remove_quotes(value):
if value is None:
value = ''
elif value.startswith('"') and value.endswith('"'):
value = value[1:-1]
elif value.startswith("'") and value.endswith("'"):
value = value[1:-1]
return value
def _process(proc_data):
"""
Final processing to conform to the schema.
Parameters:
proc_data: (Dictionary) raw structured data to process
Returns:
Dictionary representing the INI file.
"""
# remove quotation marks from beginning and end of values
for k, v in proc_data.items():
if isinstance(v, dict):
for key, value in v.items():
if isinstance(value, list):
v[key] = [_remove_quotes(x) for x in value]
else:
v[key] = _remove_quotes(value)
continue
elif isinstance(v, list):
proc_data[k] = [_remove_quotes(x) for x in v]
else:
proc_data[k] = _remove_quotes(v)
return proc_data
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Dictionary representing the INI file.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output = {}
if jc.utils.has_data(data):
ini_parser = configparser.ConfigParser(
dict_type = MultiDict,
allow_no_value=True,
interpolation=None,
default_section=None,
empty_lines_in_values=False,
strict=False
)
# don't convert keys to lower-case:
ini_parser.optionxform = lambda option: option
try:
ini_parser.read_string(data)
raw_output = {s: dict(ini_parser.items(s)) for s in ini_parser.sections()}
except configparser.MissingSectionHeaderError:
# find a top-level section name that will not collide with any existing ones
while True:
my_uuid = str(uuid.uuid4())
if my_uuid not in data:
break
data = f'[{my_uuid}]\n' + data
ini_parser.read_string(data)
temp_dict = {s: dict(ini_parser.items(s)) for s in ini_parser.sections()}
# move items under fake top-level sections to the root
raw_output = temp_dict.pop(my_uuid)
# get the rest of the sections
raw_output.update(temp_dict)
return raw_output if raw else _process(raw_output)

View File

@@ -46,7 +46,6 @@ Schema:
}
]
Examples:
$ iwconfig | jc --iwconfig -p
@@ -78,7 +77,6 @@ Examples:
"missed_beacon": 0
}
]
"""
import re
from typing import List, Dict

View File

@@ -21,8 +21,8 @@ Usage (module):
Schema:
key/value document converted to a dictionary - see the configparser standard
library documentation for more details.
Key/Value document converted to a dictionary - see the python configparser
standard library documentation for more details.
{
"key1": string,
@@ -36,6 +36,7 @@ Examples:
name = John Doe
address=555 California Drive
age: 34
; comments can include # or ;
# delimiter can be = or :
# quoted values have quotation marks stripped by default
@@ -50,15 +51,17 @@ Examples:
"occupation": "Engineer"
}
"""
import jc.utils
import configparser
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.2'
version = '2.0'
description = 'Key/Value file and string parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
details = 'This is a wrapper for the INI parser'
details = 'Using configparser from the python standard library'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['generic', 'file', 'string']
@@ -66,12 +69,36 @@ class info():
__version__ = info.version
def _process(proc_data):
"""
Final processing to conform to the schema.
Parameters:
proc_data: (Dictionary) raw structured data to process
Returns:
Dictionary representing a Key/Value pair document.
"""
# remove quotation marks from beginning and end of values
for key in proc_data:
if proc_data[key] is None:
proc_data[key] = ''
elif proc_data[key].startswith('"') and proc_data[key].endswith('"'):
proc_data[key] = proc_data[key][1:-1]
elif proc_data[key].startswith("'") and proc_data[key].endswith("'"):
proc_data[key] = proc_data[key][1:-1]
return proc_data
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
Note: this is just a wrapper for jc.parsers.ini
Parameters:
data: (string) text data to parse
@@ -80,7 +107,30 @@ def parse(data, raw=False, quiet=False):
Returns:
Dictionary representing the key/value file
Dictionary representing a Key/Value pair document.
"""
import jc.parsers.ini
return jc.parsers.ini.parse(data, raw=raw, quiet=quiet)
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output = {}
if jc.utils.has_data(data):
kv_parser = configparser.ConfigParser(
allow_no_value=True,
interpolation=None,
default_section=None,
strict=False
)
# don't convert keys to lower-case:
kv_parser.optionxform = lambda option: option
data = '[data]\n' + data
kv_parser.read_string(data)
output_dict = {s: dict(kv_parser.items(s)) for s in kv_parser.sections()}
for key, value in output_dict['data'].items():
raw_output[key] = value
return raw_output if raw else _process(raw_output)

View File

@@ -97,6 +97,28 @@ Schema:
]
}
},
"videocontrol_descriptors": [
{
"<item>": {
"value": string,
"description": string,
"attributes": [
string
]
}
}
],
"videostreaming_descriptors": [
{
"<item>": {
"value": string,
"description": string,
"attributes": [
string
]
}
}
],
"endpoint_descriptors": [
{
"<item>": {
@@ -269,7 +291,7 @@ from jc.exceptions import ParseError
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.2'
version = '1.3'
description = '`lsusb` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -307,6 +329,145 @@ class _NestedDict(dict):
return self.setdefault(key, _NestedDict())
class _root_obj:
def __init__(self, name):
self.name = name
self.list = []
def _entries_for_this_bus_exist(self, bus_idx):
"""Returns true if there are object entries for the corresponding bus index"""
for item in self.list:
keyname = tuple(item.keys())[0]
if '_state' in item[keyname] and item[keyname]['_state']['bus_idx'] == bus_idx:
return True
return False
def _update_output(self, bus_idx, output_line):
"""modifies output_line dictionary for the corresponding bus index.
output_line is the self.output_line attribute from the _lsusb object."""
for item in self.list:
keyname = tuple(item.keys())[0]
if '_state' in item[keyname] and item[keyname]['_state']['bus_idx'] == bus_idx:
# is this a top level value or an attribute?
if item[keyname]['_state']['attribute_value']:
last_item = item[keyname]['_state']['last_item']
if 'attributes' not in output_line[f'{self.name}'][last_item]:
output_line[f'{self.name}'][last_item]['attributes'] = []
this_attribute = f'{keyname} {item[keyname].get("value", "")} {item[keyname].get("description", "")}'.strip()
output_line[f'{self.name}'][last_item]['attributes'].append(this_attribute)
continue
output_line[f'{self.name}'].update(item)
del output_line[f'{self.name}'][keyname]['_state']
class _descriptor_obj:
def __init__(self, name):
self.name = name
self.list = []
def _entries_for_this_bus_and_interface_idx_exist(self, bus_idx, iface_idx):
"""Returns true if there are object entries for the corresponding bus index
and interface index"""
for item in self.list:
keyname = tuple(item.keys())[0]
if '_state' in item[keyname] and item[keyname]['_state']['bus_idx'] == bus_idx \
and item[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
return True
return False
def _update_output(self, bus_idx, iface_idx, output_line):
"""modifies output_line dictionary for the corresponding bus index and
interface index. output_line is the i_desc_obj object."""
for item in self.list:
keyname = tuple(item.keys())[0]
if '_state' in item[keyname] and item[keyname]['_state']['bus_idx'] == bus_idx \
and item[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
# is this a top level value or an attribute?
if item[keyname]['_state']['attribute_value']:
last_item = item[keyname]['_state']['last_item']
if 'attributes' not in output_line[f'{self.name}'][last_item]:
output_line[f'{self.name}'][last_item]['attributes'] = []
this_attribute = f'{keyname} {item[keyname].get("value", "")} {item[keyname].get("description", "")}'.strip()
output_line[f'{self.name}'][last_item]['attributes'].append(this_attribute)
continue
output_line[f'{self.name}'].update(item)
del output_line[f'{self.name}'][keyname]['_state']
class _descriptor_list:
def __init__(self, name):
self.name = name
self.list = []
def _entries_for_this_bus_and_interface_idx_exist(self, bus_idx, iface_idx):
"""Returns true if there are object entries for the corresponding bus index
and interface index"""
for item in self.list:
keyname = tuple(item.keys())[0]
if '_state' in item[keyname] and item[keyname]['_state']['bus_idx'] == bus_idx \
and item[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
return True
return False
def _get_objects_list(self, bus_idx, iface_idx):
"""Returns a list of descriptor object dictionaries for the corresponding
bus index and interface index"""
object_collection = []
# find max number of items in this object that match the bus_idx and iface_idx
num_of_items = -1
for item in self.list:
keyname = tuple(item.keys())[0]
if '_state' in item[keyname] and item[keyname]['_state']['bus_idx'] == bus_idx \
and item[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
num_of_items = item[keyname]['_state'][f'{self.name}_idx']
# create and return the collection of objects that match the bus_idx and iface_idx
if num_of_items > -1:
for obj_idx in range(num_of_items + 1):
this_object = {}
for item in self.list:
keyname = tuple(item.keys())[0]
if '_state' in item[keyname] and item[keyname]['_state']['bus_idx'] == bus_idx \
and item[keyname]['_state']['interface_descriptor_idx'] == iface_idx \
and item[keyname]['_state'][f'{self.name}_idx'] == obj_idx:
# is this a top level value or an attribute?
if item[keyname]['_state']['attribute_value']:
last_item = item[keyname]['_state']['last_item']
if 'attributes' not in this_object[last_item]:
this_object[last_item]['attributes'] = []
this_attribute = f'{keyname} {item[keyname].get("value", "")} {item[keyname].get("description", "")}'.strip()
this_object[last_item]['attributes'].append(this_attribute)
continue
this_object.update(item)
del item[keyname]['_state']
object_collection.append(this_object)
return object_collection
class _LsUsb():
def __init__(self):
self.raw_output = []
@@ -314,27 +475,37 @@ class _LsUsb():
self.section = ''
self.old_section = ''
# section_header is formatted with the correct spacing to be used with
# jc.parsers.universal.sparse_table_parse(). Pad end of string to be at least len of 25
# this value changes for different sections (e.g. videocontrol & videostreaming)
self.normal_section_header = 'key val description'
self.larger_section_header = 'key val description'
self.bus_idx = -1
self.interface_descriptor_idx = -1
self.endpoint_descriptor_idx = -1
self.videocontrol_interface_descriptor_idx = -1
self.videostreaming_interface_descriptor_idx = -1
self.last_item = ''
self.last_indent = 0
self.attribute_value = False
self.bus_list = []
self.device_descriptor_list = []
self.configuration_descriptor_list = []
self.interface_association_list = []
self.device_descriptor = _root_obj('device_descriptor')
self.configuration_descriptor = _root_obj('configuration_descriptor')
self.interface_association = _root_obj('interface_association')
self.interface_descriptor_list = []
self.interface_descriptor_attribute_list = []
self.cdc_header_list = []
self.cdc_call_management_list = []
self.cdc_acm_list = []
self.cdc_union_list = []
self.endpoint_descriptor_list = []
self.hid_device_descriptor_list = []
self.report_descriptors_list = []
self.hub_descriptor_list = []
self.cdc_header = _descriptor_obj('cdc_header')
self.cdc_call_management = _descriptor_obj('cdc_call_management')
self.cdc_acm = _descriptor_obj('cdc_acm')
self.cdc_union = _descriptor_obj('cdc_union')
self.endpoint_descriptors = _descriptor_list('endpoint_descriptor')
self.videocontrol_interface_descriptors = _descriptor_list('videocontrol_interface_descriptor')
self.videostreaming_interface_descriptors = _descriptor_list('videostreaming_interface_descriptor')
self.hid_device_descriptor = _descriptor_obj('hid_device_descriptor')
# self.report_descriptors_list = [] # not implemented
self.hub_descriptor = _root_obj('hub_descriptor')
self.hub_port_status_list = []
self.device_qualifier_list = []
self.device_status_list = []
@@ -355,14 +526,21 @@ class _LsUsb():
# determine whether this is a top-level value item or lower-level attribute
if indent > self.last_indent and self.old_section == self.section:
self.attribute_value = True
elif indent == self.last_indent and self.attribute_value and self.old_section == self.section:
elif indent == self.last_indent and self.attribute_value \
and self.old_section == self.section:
self.attribute_value = True
else:
self.attribute_value = False
# section_header is formatted with the correct spacing to be used with
# jc.parsers.universal.sparse_table_parse(). Pad end of string to be at least len of 25
section_header = 'key val description'
section_header = self.normal_section_header
if self.section == 'videocontrol_interface_descriptor' \
or self.section == 'videostreaming_interface_descriptor':
section_header = self.larger_section_header
temp_obj = [section_header, line.strip() + (' ' * 25)]
temp_obj = sparse_table_parse(temp_obj)
@@ -377,7 +555,9 @@ class _LsUsb():
'last_item': self.last_item,
'bus_idx': self.bus_idx,
'interface_descriptor_idx': self.interface_descriptor_idx,
'endpoint_descriptor_idx': self.endpoint_descriptor_idx
'endpoint_descriptor_idx': self.endpoint_descriptor_idx,
'videocontrol_interface_descriptor_idx': self.videocontrol_interface_descriptor_idx,
'videostreaming_interface_descriptor_idx': self.videostreaming_interface_descriptor_idx
}
}
}
@@ -429,12 +609,15 @@ class _LsUsb():
self.attribute_value = False
return True
# bus information is on the same line so need to extract data immediately and set indexes
# bus information is on the same line so need to extract data
# immediately and set indexes
if line.startswith('Bus '):
self.section = 'bus'
self.bus_idx += 1
self.interface_descriptor_idx = -1
self.endpoint_descriptor_idx = -1
self.videocontrol_interface_descriptor_idx = -1
self.videostreaming_interface_descriptor_idx = -1
self.attribute_value = False
line_split = line.strip().split(maxsplit=6)
self.bus_list.append(
@@ -442,7 +625,8 @@ class _LsUsb():
'bus': line_split[1],
'device': line_split[3][:-1],
'id': line_split[5],
'description': (line_split[6:7] or [None])[0], # way to get a list item or None
# way to get a list item or None
'description': (line_split[6:7] or [None])[0],
'_state': {
'bus_idx': self.bus_idx
}
@@ -450,22 +634,36 @@ class _LsUsb():
)
return True
# This section is a list, so need to update indexes
# These sections are lists, so need to update indexes
if line.startswith(' Interface Descriptor:'):
self.section = 'interface_descriptor'
self.interface_descriptor_idx += 1
self.endpoint_descriptor_idx = -1
self.videocontrol_interface_descriptor_idx = -1
self.videostreaming_interface_descriptor_idx = -1
self.attribute_value = False
return True
# This section is a list, so need to update the index
if line.startswith(' Endpoint Descriptor:'):
self.section = 'endpoint_descriptor'
self.endpoint_descriptor_idx += 1
self.attribute_value = False
return True
# some device status information is displayed on the initial line so need to extract immediately
if line.startswith(' VideoControl Interface Descriptor:'):
self.section = 'videocontrol_interface_descriptor'
self.videocontrol_interface_descriptor_idx += 1
self.attribute_value = False
return True
if line.startswith(' VideoStreaming Interface Descriptor:'):
self.section = 'videostreaming_interface_descriptor'
self.videostreaming_interface_descriptor_idx += 1
self.attribute_value = False
return True
# some device status information is displayed on the initial line so
# need to extract immediately
if line.startswith('Device Status:'):
self.section = 'device_status'
self.attribute_value = False
@@ -507,18 +705,20 @@ class _LsUsb():
def _populate_lists(self, line):
section_list_map = {
'device_descriptor': self.device_descriptor_list,
'configuration_descriptor': self.configuration_descriptor_list,
'interface_association': self.interface_association_list,
'device_descriptor': self.device_descriptor.list,
'configuration_descriptor': self.configuration_descriptor.list,
'interface_association': self.interface_association.list,
'interface_descriptor': self.interface_descriptor_list,
'cdc_header': self.cdc_header_list,
'cdc_call_management': self.cdc_call_management_list,
'cdc_acm': self.cdc_acm_list,
'cdc_union': self.cdc_union_list,
'hid_device_descriptor': self.hid_device_descriptor_list,
'report_descriptors': self.report_descriptors_list,
'endpoint_descriptor': self.endpoint_descriptor_list,
'hub_descriptor': self.hub_descriptor_list,
'cdc_header': self.cdc_header.list,
'cdc_call_management': self.cdc_call_management.list,
'cdc_acm': self.cdc_acm.list,
'cdc_union': self.cdc_union.list,
'hid_device_descriptor': self.hid_device_descriptor.list,
# 'report_descriptors': self.report_descriptors_list, # not implemented
'videocontrol_interface_descriptor': self.videocontrol_interface_descriptors.list,
'videostreaming_interface_descriptor': self.videostreaming_interface_descriptors.list,
'endpoint_descriptor': self.endpoint_descriptors.list,
'hub_descriptor': self.hub_descriptor.list,
'device_qualifier': self.device_qualifier_list
}
@@ -528,7 +728,9 @@ class _LsUsb():
return True
# special handling of these sections
if line.startswith(' ') and self.section == 'hub_port_status':
if line.startswith(' ') and not line.startswith(' ') \
and self.section == 'hub_port_status':
self.hub_port_status_list.append(self._add_hub_port_status_attributes(line))
return True
@@ -547,6 +749,10 @@ class _LsUsb():
['device_descriptor']['configuration_descriptor']['interface_association'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'] = []
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['videocontrol_interface_descriptors'] = []
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['videocontrol_interface_descriptors'][0] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['videostreaming_interface_descriptors'] = []
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['videostreaming_interface_descriptors'][0] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['cdc_header'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['cdc_call_management'] = {}
['device_descriptor']['configuration_descriptor']['interface_descriptors'][0]['cdc_acm'] = {}
@@ -568,81 +774,56 @@ class _LsUsb():
del item['_state']
self.output_line.update(item)
for dd in self.device_descriptor_list:
keyname = tuple(dd.keys())[0]
if '_state' in dd[keyname] and dd[keyname]['_state']['bus_idx'] == idx:
# add initial root-level keys
if self.device_descriptor._entries_for_this_bus_exist(idx):
self.device_descriptor._update_output(idx, self.output_line)
# is this a top level value or an attribute?
if dd[keyname]['_state']['attribute_value']:
last_item = dd[keyname]['_state']['last_item']
if 'attributes' not in self.output_line['device_descriptor'][last_item]:
self.output_line['device_descriptor'][last_item]['attributes'] = []
if self.configuration_descriptor._entries_for_this_bus_exist(idx):
self.configuration_descriptor._update_output(
idx, self.output_line['device_descriptor']
)
this_attribute = f'{keyname} {dd[keyname].get("value", "")} {dd[keyname].get("description", "")}'.strip()
self.output_line['device_descriptor'][last_item]['attributes'].append(this_attribute)
continue
if self.interface_association._entries_for_this_bus_exist(idx):
self.interface_association._update_output(
idx, self.output_line['device_descriptor']['configuration_descriptor']
)
self.output_line['device_descriptor'].update(dd)
del self.output_line['device_descriptor'][keyname]['_state']
for cd in self.configuration_descriptor_list:
keyname = tuple(cd.keys())[0]
if '_state' in cd[keyname] and cd[keyname]['_state']['bus_idx'] == idx:
# is this a top level value or an attribute?
if cd[keyname]['_state']['attribute_value']:
last_item = cd[keyname]['_state']['last_item']
if 'attributes' not in self.output_line['device_descriptor']['configuration_descriptor'][last_item]:
self.output_line['device_descriptor']['configuration_descriptor'][last_item]['attributes'] = []
this_attribute = f'{keyname} {cd[keyname].get("value", "")} {cd[keyname].get("description", "")}'.strip()
self.output_line['device_descriptor']['configuration_descriptor'][last_item]['attributes'].append(this_attribute)
continue
self.output_line['device_descriptor']['configuration_descriptor'].update(cd)
del self.output_line['device_descriptor']['configuration_descriptor'][keyname]['_state']
for ia in self.interface_association_list:
keyname = tuple(ia.keys())[0]
if '_state' in ia[keyname] and ia[keyname]['_state']['bus_idx'] == idx:
# is this a top level value or an attribute?
if ia[keyname]['_state']['attribute_value']:
last_item = ia[keyname]['_state']['last_item']
if 'attributes' not in self.output_line['device_descriptor']['configuration_descriptor']['interface_association'][last_item]:
self.output_line['device_descriptor']['configuration_descriptor']['interface_association'][last_item]['attributes'] = []
this_attribute = f'{keyname} {ia[keyname].get("value", "")} {ia[keyname].get("description", "")}'.strip()
self.output_line['device_descriptor']['configuration_descriptor']['interface_association'][last_item]['attributes'].append(this_attribute)
continue
self.output_line['device_descriptor']['configuration_descriptor']['interface_association'].update(ia)
del self.output_line['device_descriptor']['configuration_descriptor']['interface_association'][keyname]['_state']
# add interface_descriptor key if it doesn't exist and there are entries for this bus
# add interface_descriptor key if it doesn't exist and there
# are entries for this bus
for iface_attrs in self.interface_descriptor_list:
keyname = tuple(iface_attrs.keys())[0]
if '_state' in iface_attrs[keyname] and iface_attrs[keyname]['_state']['bus_idx'] == idx:
if '_state' in iface_attrs[keyname] \
and iface_attrs[keyname]['_state']['bus_idx'] == idx:
self.output_line['device_descriptor']['configuration_descriptor']['interface_descriptors'] = []
# find max index for this bus idx, then iterate over that range
i_desc_iters = -1
for iface_attrs in self.interface_descriptor_list:
keyname = tuple(iface_attrs.keys())[0]
if '_state' in iface_attrs[keyname] and iface_attrs[keyname]['_state']['bus_idx'] == idx:
if '_state' in iface_attrs[keyname] \
and iface_attrs[keyname]['_state']['bus_idx'] == idx:
i_desc_iters = iface_attrs[keyname]['_state']['interface_descriptor_idx']
# create the interface descriptor object
if i_desc_iters > -1:
for iface_idx in range(i_desc_iters + 1):
i_desc_obj = _NestedDict()
for iface_attrs in self.interface_descriptor_list:
keyname = tuple(iface_attrs.keys())[0]
if '_state' in iface_attrs[keyname] and iface_attrs[keyname]['_state']['bus_idx'] == idx and iface_attrs[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
if '_state' in iface_attrs[keyname] \
and iface_attrs[keyname]['_state']['bus_idx'] == idx \
and iface_attrs[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
# is this a top level value or an attribute?
if iface_attrs[keyname]['_state']['attribute_value']:
last_item = iface_attrs[keyname]['_state']['last_item']
if 'attributes' not in i_desc_obj[last_item]:
i_desc_obj[last_item]['attributes'] = []
@@ -653,168 +834,70 @@ class _LsUsb():
del iface_attrs[keyname]['_state']
i_desc_obj.update(iface_attrs)
# add other nodes to the object (cdc_header, endpoint descriptors, etc.)
for ch in self.cdc_header_list:
keyname = tuple(ch.keys())[0]
if '_state' in ch[keyname] and ch[keyname]['_state']['bus_idx'] == idx and ch[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
# add the rest of the interface descriptor keys to the object
if self.cdc_header._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.cdc_header._update_output(idx, iface_idx, i_desc_obj)
# is this a top level value or an attribute?
if ch[keyname]['_state']['attribute_value']:
last_item = ch[keyname]['_state']['last_item']
if 'attributes' not in i_desc_obj['cdc_header'][last_item]:
i_desc_obj['cdc_header'][last_item]['attributes'] = []
if self.cdc_call_management._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.cdc_call_management._update_output(idx, iface_idx, i_desc_obj)
this_attribute = f'{keyname} {ch[keyname].get("value", "")} {ch[keyname].get("description", "")}'.strip()
i_desc_obj['cdc_header'][last_item]['attributes'].append(this_attribute)
continue
if self.cdc_acm._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.cdc_acm._update_output(idx, iface_idx, i_desc_obj)
i_desc_obj['cdc_header'].update(ch)
del i_desc_obj['cdc_header'][keyname]['_state']
if self.cdc_union._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.cdc_union._update_output(idx, iface_idx, i_desc_obj)
for ccm in self.cdc_call_management_list:
keyname = tuple(ccm.keys())[0]
if '_state' in ccm[keyname] and ccm[keyname]['_state']['bus_idx'] == idx and ccm[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
if self.hid_device_descriptor._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
self.hid_device_descriptor._update_output(idx, iface_idx, i_desc_obj)
# is this a top level value or an attribute?
if ccm[keyname]['_state']['attribute_value']:
last_item = ccm[keyname]['_state']['last_item']
if 'attributes' not in i_desc_obj['cdc_call_management'][last_item]:
i_desc_obj['cdc_call_management'][last_item]['attributes'] = []
# Not Implemented: Report Descriptors (need more samples)
# for rd in self.report_descriptors_list:
# keyname = tuple(rd.keys())[0]
# if '_state' in rd[keyname] and rd[keyname]['_state']['bus_idx'] == idx and rd[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
# i_desc_obj['hid_device_descriptor']['report_descriptors'].update(rd)
# del i_desc_obj['hid_device_descriptor']['report_descriptors'][keyname]['_state']
this_attribute = f'{keyname} {ccm[keyname].get("value", "")} {ccm[keyname].get("description", "")}'.strip()
i_desc_obj['cdc_call_management'][last_item]['attributes'].append(this_attribute)
continue
if self.videocontrol_interface_descriptors._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
i_desc_obj['videocontrol_interface_descriptors'] = []
i_desc_obj['videocontrol_interface_descriptors'].extend(
self.videocontrol_interface_descriptors._get_objects_list(idx, iface_idx)
)
i_desc_obj['cdc_call_management'].update(ccm)
del i_desc_obj['cdc_call_management'][keyname]['_state']
if self.videostreaming_interface_descriptors._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
i_desc_obj['videostreaming_interface_descriptors'] = []
i_desc_obj['videostreaming_interface_descriptors'].extend(
self.videostreaming_interface_descriptors._get_objects_list(idx, iface_idx)
)
for ca in self.cdc_acm_list:
keyname = tuple(ca.keys())[0]
if '_state' in ca[keyname] and ca[keyname]['_state']['bus_idx'] == idx and ca[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
# is this a top level value or an attribute?
if ca[keyname]['_state']['attribute_value']:
last_item = ca[keyname]['_state']['last_item']
if 'attributes' not in i_desc_obj['cdc_acm'][last_item]:
i_desc_obj['cdc_acm'][last_item]['attributes'] = []
this_attribute = f'{keyname} {ca[keyname].get("value", "")} {ca[keyname].get("description", "")}'.strip()
i_desc_obj['cdc_acm'][last_item]['attributes'].append(this_attribute)
continue
i_desc_obj['cdc_acm'].update(ca)
del i_desc_obj['cdc_acm'][keyname]['_state']
for cu in self.cdc_union_list:
keyname = tuple(cu.keys())[0]
if '_state' in cu[keyname] and cu[keyname]['_state']['bus_idx'] == idx and cu[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
# is this a top level value or an attribute?
if cu[keyname]['_state']['attribute_value']:
last_item = cu[keyname]['_state']['last_item']
if 'attributes' not in i_desc_obj['cdc_union'][last_item]:
i_desc_obj['cdc_union'][last_item]['attributes'] = []
this_attribute = f'{keyname} {cu[keyname].get("value", "")} {cu[keyname].get("description", "")}'.strip()
i_desc_obj['cdc_union'][last_item]['attributes'].append(this_attribute)
continue
i_desc_obj['cdc_union'].update(cu)
del i_desc_obj['cdc_union'][keyname]['_state']
for hidd in self.hid_device_descriptor_list:
keyname = tuple(hidd.keys())[0]
if '_state' in hidd[keyname] and hidd[keyname]['_state']['bus_idx'] == idx and hidd[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
# is this a top level value or an attribute?
if hidd[keyname]['_state']['attribute_value']:
last_item = hidd[keyname]['_state']['last_item']
if 'attributes' not in i_desc_obj['hid_device_descriptor'][last_item]:
i_desc_obj['hid_device_descriptor'][last_item]['attributes'] = []
this_attribute = f'{keyname} {hidd[keyname].get("value", "")} {hidd[keyname].get("description", "")}'.strip()
i_desc_obj['hid_device_descriptor'][last_item]['attributes'].append(this_attribute)
continue
i_desc_obj['hid_device_descriptor'].update(hidd)
del i_desc_obj['hid_device_descriptor'][keyname]['_state']
# Not Implemented: Report Descriptors (need more samples)
# for rd in self.report_descriptors_list:
# keyname = tuple(rd.keys())[0]
# if '_state' in rd[keyname] and rd[keyname]['_state']['bus_idx'] == idx and rd[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
# i_desc_obj['hid_device_descriptor']['report_descriptors'].update(rd)
# del i_desc_obj['hid_device_descriptor']['report_descriptors'][keyname]['_state']
# add endpoint_descriptor key if it doesn't exist and there are entries for this interface_descriptor
for endpoint_attrs in self.endpoint_descriptor_list:
keyname = tuple(endpoint_attrs.keys())[0]
if '_state' in endpoint_attrs[keyname] and endpoint_attrs[keyname]['_state']['bus_idx'] == idx and endpoint_attrs[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
i_desc_obj['endpoint_descriptors'] = []
# find max index for this endpoint_descriptor idx, then iterate over that range
e_desc_iters = -1
for endpoint_attrs in self.endpoint_descriptor_list:
keyname = tuple(endpoint_attrs.keys())[0]
if '_state' in endpoint_attrs[keyname] and endpoint_attrs[keyname]['_state']['bus_idx'] == idx and endpoint_attrs[keyname]['_state']['interface_descriptor_idx'] == iface_idx:
e_desc_iters = endpoint_attrs[keyname]['_state']['endpoint_descriptor_idx']
# create the endpoint descriptor object
if e_desc_iters > -1:
for endpoint_idx in range(e_desc_iters + 1):
e_desc_obj = {}
for endpoint_attrs in self.endpoint_descriptor_list:
keyname = tuple(endpoint_attrs.keys())[0]
if '_state' in endpoint_attrs[keyname] and endpoint_attrs[keyname]['_state']['bus_idx'] == idx and endpoint_attrs[keyname]['_state']['interface_descriptor_idx'] == iface_idx and endpoint_attrs[keyname]['_state']['endpoint_descriptor_idx'] == endpoint_idx:
# is this a top level value or an attribute?
if endpoint_attrs[keyname]['_state']['attribute_value']:
last_item = endpoint_attrs[keyname]['_state']['last_item']
if 'attributes' not in e_desc_obj[last_item]:
e_desc_obj[last_item]['attributes'] = []
this_attribute = f'{keyname} {endpoint_attrs[keyname].get("value", "")} {endpoint_attrs[keyname].get("description", "")}'.strip()
e_desc_obj[last_item]['attributes'].append(this_attribute)
continue
e_desc_obj.update(endpoint_attrs)
del endpoint_attrs[keyname]['_state']
i_desc_obj['endpoint_descriptors'].append(e_desc_obj)
if self.endpoint_descriptors._entries_for_this_bus_and_interface_idx_exist(idx, iface_idx):
i_desc_obj['endpoint_descriptors'] = []
i_desc_obj['endpoint_descriptors'].extend(
self.endpoint_descriptors._get_objects_list(idx, iface_idx)
)
# add the object to the list of interface descriptors
self.output_line['device_descriptor']['configuration_descriptor']['interface_descriptors'].append(i_desc_obj)
for hd in self.hub_descriptor_list:
keyname = tuple(hd.keys())[0]
if '_state' in hd[keyname] and hd[keyname]['_state']['bus_idx'] == idx:
# is this a top level value or an attribute?
if hd[keyname]['_state']['attribute_value']:
last_item = hd[keyname]['_state']['last_item']
if 'attributes' not in self.output_line['hub_descriptor'][last_item]:
self.output_line['hub_descriptor'][last_item]['attributes'] = []
this_attribute = f'{keyname} {hd[keyname].get("value", "")} {hd[keyname].get("description", "")}'.strip()
self.output_line['hub_descriptor'][last_item]['attributes'].append(this_attribute)
continue
self.output_line['hub_descriptor'].update(hd)
del self.output_line['hub_descriptor'][keyname]['_state']
# add final root-level keys
if self.hub_descriptor._entries_for_this_bus_exist(idx):
self.hub_descriptor._update_output(idx, self.output_line)
for hps in self.hub_port_status_list:
keyname = tuple(hps.keys())[0]
if '_state' in hps[keyname] and hps[keyname]['_state']['bus_idx'] == idx:
self.output_line['hub_descriptor']['hub_port_status'].update(hps)
del self.output_line['hub_descriptor']['hub_port_status'][keyname]['_state']
for dq in self.device_qualifier_list:
keyname = tuple(dq.keys())[0]
if '_state' in dq[keyname] and dq[keyname]['_state']['bus_idx'] == idx:
self.output_line['device_qualifier'].update(dq)
del self.output_line['device_qualifier'][keyname]['_state']
for ds in self.device_status_list:
if '_state' in ds and ds['_state']['bus_idx'] == idx:
self.output_line['device_status'].update(ds)
del self.output_line['device_status']['_state']

View File

@@ -20,7 +20,7 @@ Schema:
"filesystem": string,
"mount_point": string,
"type": string,
"access": [
"options": [
string
]
}
@@ -34,7 +34,7 @@ Example:
"filesystem": "sysfs",
"mount_point": "/sys",
"type": "sysfs",
"access": [
"options": [
"rw",
"nosuid",
"nodev",
@@ -46,7 +46,7 @@ Example:
"filesystem": "proc",
"mount_point": "/proc",
"type": "proc",
"access": [
"options": [
"rw",
"nosuid",
"nodev",
@@ -58,7 +58,7 @@ Example:
"filesystem": "udev",
"mount_point": "/dev",
"type": "devtmpfs",
"access": [
"options": [
"rw",
"nosuid",
"relatime",
@@ -75,11 +75,11 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.7'
version = '1.8'
description = '`mount` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'freebsd']
compatible = ['linux', 'darwin', 'freebsd', 'aix']
magic_commands = ['mount']
tags = ['command']
@@ -138,10 +138,38 @@ def _linux_parse(data):
output_line['filesystem'] = parsed_line[0]
output_line['mount_point'] = parsed_line[2]
output_line['type'] = parsed_line[4]
output_line['options'] = parsed_line[5].lstrip('(').rstrip(')').split(',')
options = parsed_line[5].lstrip('(').rstrip(')').split(',')
output.append(output_line)
output_line['options'] = options
return output
def _aix_parse(data):
output = []
# AIX mount command starts with these headers:
# node mounted mounted over vfs date options
# -------- --------------- --------------- ------ ------------ ---------------
# Remove them
data.pop(0)
data.pop(0)
for entry in data:
output_line = {}
parsed_line = entry.split()
# AIX mount entries have the remote node as the zeroth element. If the
# mount is local, the zeroth element is the filesystem instead. We can
# detect this by the lenth of the list. For local mounts, length is 7,
# and for remote mounts, the length is 8. In the remote case, pop off
# the zeroth element. Then parsed_line has a consistent format.
if len(parsed_line) == 8:
parsed_line.pop(0)
output_line['filesystem'] = parsed_line[0]
output_line['mount_point'] = parsed_line[1]
output_line['type'] = parsed_line[2]
output_line['options'] = parsed_line[6].lstrip('(').rstrip(')').split(',')
output.append(output_line)
@@ -171,9 +199,12 @@ def parse(data, raw=False, quiet=False):
if jc.utils.has_data(data):
# check for OSX output
# check for OSX and AIX output
if ' type ' not in cleandata[0]:
raw_output = _osx_parse(cleandata)
if 'node' in cleandata[0]:
raw_output = _aix_parse(cleandata)
else:
raw_output = _osx_parse(cleandata)
else:
raw_output = _linux_parse(cleandata)

View File

@@ -109,7 +109,8 @@ Examples:
"mw",
"me",
"dw",
"sd"
"sd",
"mp"
],
"VmFlags_pretty": [
"readable",
@@ -211,6 +212,7 @@ def _process(proc_data: List[Dict]) -> List[Dict]:
'mw': 'may write',
'me': 'may execute',
'ms': 'may share',
'mp': 'MPX-specific VMA',
'gd': 'stack segment growns down',
'pf': 'pure PFN range',
'dw': 'disabled write to the mapped file',
@@ -274,10 +276,10 @@ def parse(
if jc.utils.has_data(data):
map_line = re.compile(r'''
^(?P<start>[0-9a-f]{12,16})-
(?P<end>[0-9a-f]{12,16})\s
^(?P<start>[0-9a-f]{8,16})-
(?P<end>[0-9a-f]{8,16})\s
(?P<perms>[rwxsp\-]{4})\s
(?P<offset>[0-9a-f]{8})\s
(?P<offset>[0-9a-f]{8,9})\s
(?P<maj>[0-9a-f]{2}):
(?P<min>[0-9a-f]{2})\s
(?P<inode>\d+)\s+

18
jc/parsers/pyedid/LICENSE Normal file
View File

@@ -0,0 +1,18 @@
Copyright 2019-2020 Jonas Lieb, Davydov Denis
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.

View File

171
jc/parsers/pyedid/edid.py Executable file
View File

@@ -0,0 +1,171 @@
"""
Edid module
"""
import struct
from collections import namedtuple
from typing import ByteString
__all__ = ["Edid"]
class Edid:
"""Edid class
Raises:
`ValueError`: if invalid edid data
"""
_STRUCT_FORMAT = (
"<" # little-endian
"8s" # constant header (8 bytes)
"H" # manufacturer id (2 bytes)
"H" # product id (2 bytes)
"I" # serial number (4 bytes)
"B" # manufactoring week (1 byte)
"B" # manufactoring year (1 byte)
"B" # edid version (1 byte)
"B" # edid revision (1 byte)
"B" # video input type (1 byte)
"B" # horizontal size in cm (1 byte)
"B" # vertical size in cm (1 byte)
"B" # display gamma (1 byte)
"B" # supported features (1 byte)
"10s" # color characteristics (10 bytes)
"H" # supported timings (2 bytes)
"B" # reserved timing (1 byte)
"16s" # EDID supported timings (16 bytes)
"18s" # detailed timing block 1 (18 bytes)
"18s" # detailed timing block 2 (18 bytes)
"18s" # detailed timing block 3 (18 bytes)
"18s" # detailed timing block 4 (18 bytes)
"B" # extension flag (1 byte)
"B"
) # checksum (1 byte)
_TIMINGS = {
0: (1280, 1024, 75.0),
1: (1024, 768, 75.0),
2: (1024, 768, 70.0),
3: (1024, 768, 60.0),
4: (1024, 768, 87.0),
5: (832, 624, 75.0),
6: (800, 600, 75.0),
7: (800, 600, 72.0),
8: (800, 600, 60.0),
9: (800, 600, 56.0),
10: (640, 480, 75.0),
11: (640, 480, 72.0),
12: (640, 480, 67.0),
13: (640, 480, 60.0),
14: (720, 400, 88.0),
15: (720, 400, 70.0),
}
_ASPECT_RATIOS = {
0b00: (16, 10),
0b01: ( 4, 3),
0b10: ( 5, 4),
0b11: (16, 9),
}
_RawEdid = namedtuple("RawEdid",
("header",
"manu_id",
"prod_id",
"serial_no",
"manu_week",
"manu_year",
"edid_version",
"edid_revision",
"input_type",
"width",
"height",
"gamma",
"features",
"color",
"timings_supported",
"timings_reserved",
"timings_edid",
"timing_1",
"timing_2",
"timing_3",
"timing_4",
"extension",
"checksum")
)
def __init__(self, edid: ByteString):
self._parse_edid(edid)
def _parse_edid(self, edid: ByteString):
"""Convert edid byte string to edid object"""
if struct.calcsize(self._STRUCT_FORMAT) != 128:
raise ValueError("Wrong edid size.")
if sum(map(int, edid)) % 256 != 0:
raise ValueError("Checksum mismatch.")
unpacked = struct.unpack(self._STRUCT_FORMAT, edid)
raw_edid = self._RawEdid(*unpacked)
if raw_edid.header != b'\x00\xff\xff\xff\xff\xff\xff\x00':
raise ValueError("Invalid header.")
self.raw = edid
self.manufacturer_id = raw_edid.manu_id
self.product = raw_edid.prod_id
self.year = raw_edid.manu_year + 1990
self.edid_version = "{:d}.{:d}".format(raw_edid.edid_version, raw_edid.edid_revision)
self.type = "digital" if (raw_edid.input_type & 0xFF) else "analog"
self.width = float(raw_edid.width)
self.height = float(raw_edid.height)
self.gamma = (raw_edid.gamma+100)/100
self.dpms_standby = bool(raw_edid.features & 0xFF)
self.dpms_suspend = bool(raw_edid.features & 0x7F)
self.dpms_activeoff = bool(raw_edid.features & 0x3F)
self.resolutions = []
for i in range(16):
bit = raw_edid.timings_supported & (1 << i)
if bit:
self.resolutions.append(self._TIMINGS[i])
for i in range(8):
bytes_data = raw_edid.timings_edid[2*i:2*i+2]
if bytes_data == b'\x01\x01':
continue
byte1, byte2 = bytes_data
x_res = 8*(int(byte1)+31)
aspect_ratio = self._ASPECT_RATIOS[(byte2>>6) & 0b11]
y_res = int(x_res * aspect_ratio[1]/aspect_ratio[0])
rate = (int(byte2) & 0b00111111) + 60.0
self.resolutions.append((x_res, y_res, rate))
self.name = None
self.serial = None
for timing_bytes in (raw_edid.timing_1, raw_edid.timing_2, raw_edid.timing_3, raw_edid.timing_4):
# "other" descriptor
if timing_bytes[0:2] == b'\x00\x00':
timing_type = timing_bytes[3]
if timing_type in (0xFF, 0xFE, 0xFC):
buffer = timing_bytes[5:]
buffer = buffer.partition(b"\x0a")[0]
text = buffer.decode("cp437")
if timing_type == 0xFF:
self.serial = text
elif timing_type == 0xFC:
self.name = text
if not self.serial:
self.serial = raw_edid.serial_no
def __repr__(self):
clsname = self.__class__.__name__
attributes = []
for name in dir(self):
if not name.startswith("_"):
value = getattr(self, name)
attributes.append("\t{}={}".format(name, value))
return "{}(\n{}\n)".format(clsname, ", \n".join(attributes))

View File

View File

@@ -0,0 +1,61 @@
"""
EDID helper
"""
from subprocess import CalledProcessError, check_output
from typing import ByteString, List
__all__ = ["EdidHelper"]
class EdidHelper:
"""Class for working with EDID data"""
@staticmethod
def hex2bytes(hex_data: str) -> ByteString:
"""Convert hex EDID string to bytes
Args:
hex_data (str): hex edid string
Returns:
ByteString: edid byte string
"""
# delete edid 1.3 additional block
if len(hex_data) > 256:
hex_data = hex_data[:256]
numbers = []
for i in range(0, len(hex_data), 2):
pair = hex_data[i : i + 2]
numbers.append(int(pair, 16))
return bytes(numbers)
@classmethod
def get_edids(cls) -> List[ByteString]:
"""Get edids from xrandr
Raises:
`RuntimeError`: if error with retrieving xrandr util data
Returns:
List[ByteString]: list with edids
"""
try:
output = check_output(["xrandr", "--verbose"])
except (CalledProcessError, FileNotFoundError) as err:
raise RuntimeError(
"Error retrieving xrandr util data: {}".format(err)
) from None
edids = []
lines = output.splitlines()
for i, line in enumerate(lines):
line = line.decode().strip()
if line.startswith("EDID:"):
selection = lines[i + 1 : i + 9]
selection = list(s.decode().strip() for s in selection)
selection = "".join(selection)
bytes_section = cls.hex2bytes(selection)
edids.append(bytes_section)
return edids

View File

@@ -0,0 +1,136 @@
"""
Module for working with PNP ID REGISTRY
"""
import csv
import string
from html.parser import HTMLParser
from urllib import request
__all__ = ["Registry"]
class WebPnpIdParser(HTMLParser):
"""Parser pnp id from https://uefi.org/PNP_ID_List
Examples:
p = WebPnpIdParser()
p.feed(html_data)
p.result
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._find_table = False
self._find_row = False
# first -- company name, second -- pnp id, third -- approved date
self._last_field = []
# key -- pnp id, value -- tuple (company_name, approved_date)
self.result = {}
def handle_starttag(self, tag, attrs):
if tag == "tbody":
self._find_table = True
elif self._find_table and tag == "tr":
self._find_row = True
def handle_endtag(self, tag):
if tag == "tbody":
self._find_table = False
elif self._find_table and tag == "tr":
self._find_row = False
# add table row to result
self.result[self._last_field[1]] = (
self._last_field[0],
self._last_field[-1],
)
self._last_field.clear()
def handle_data(self, data):
# skip processing until table is found
if not self._find_table:
return
if self._find_row:
data = data.strip()
if data:
self._last_field.append(data)
def error(self, message):
super().close()
class Registry(dict):
"""Registry pnp id data dictionary
key -- pnp_id
value -- company name
"""
@classmethod
def from_web(cls, filter_by_id: str = None):
"""Get registry from https://uefi.org/PNP_ID_List
Args:
filter_by_id (str), optional: filter registry by id
Raises:
Returns:
"""
url = "https://uefi.org/PNP_ID_List"
if filter_by_id:
url += "?search={}".format(filter_by_id)
with request.urlopen(url) as req:
parse = WebPnpIdParser()
parse.feed(req.read().decode())
registry = cls()
for key, value in parse.result.items():
# skip invalid search value
if filter_by_id and key != filter_by_id:
continue
registry[key] = value[0]
return registry
@classmethod
def from_csv(cls, csv_path: str, filter_by_id: str = None):
"""Get registry by csv local file
Args:
csv_path (str): path to csv file
filter_by_id (str), optional: filter registry by id
Raises:
Returns:
"""
registry = cls()
with open(csv_path, "r") as file:
reader = csv.reader(file)
for line in reader:
# filter
if filter_by_id and filter_by_id != line[0]:
continue
registry[line[0]] = line[1]
return registry
def to_csv(self, csv_path: str):
"""Dump registry to csv file"""
with open(csv_path, "w") as csv_file:
writer = csv.writer(csv_file)
writer.writerows(self.items())
return self
def get_company_from_id(self, pnp_id: str) -> str:
"""Convert PNP id to company name"""
return self.get(pnp_id, "Unknown")
def get_company_from_raw(self, raw: int) -> str:
"""Convert raw edid value to company name"""
tmp = [(raw >> 10) & 31, (raw >> 5) & 31, raw & 31]
pnp_id = "".join(string.ascii_uppercase[n - 1] for n in tmp)
return self.get_company_from_id(pnp_id)

29
jc/parsers/pyedid/main.py Normal file
View File

@@ -0,0 +1,29 @@
"""
Entrypoint
"""
from pyedid.edid import Edid
from pyedid.helpers.edid_helper import EdidHelper
from pyedid.helpers.registry import Registry
def main():
"""Main func"""
edid_csv_cache = "/tmp/pyedid-database.csv"
try:
registry = Registry.from_csv(edid_csv_cache)
except FileNotFoundError:
print("Loading registry from web...")
registry = Registry.from_web()
print("Done!\n")
registry.to_csv(edid_csv_cache)
for raw in EdidHelper.get_edids():
edid = Edid(raw, registry)
print(edid)
if __name__ == "__main__":
main()

View File

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

688
jc/parsers/ssh_conf.py Normal file
View File

@@ -0,0 +1,688 @@
"""jc - JSON Convert `ssh` configuration file and `ssh -G` command output parser
This parser will work with `ssh` configuration files or the output of
`ssh -G`. Any `Match` blocks in the `ssh` configuration file will be
ignored.
Usage (cli):
$ ssh -G hostname | jc --ssh-conf
or
$ jc ssh -G hostname
or
$ cat ~/.ssh/config | jc --ssh-conf
Usage (module):
import jc
result = jc.parse('ssh_conf', ssh_conf_output)
Schema:
[
{
"host": string,
"host_list": [
string
],
"addkeystoagent": string,
"addressfamily": string,
"batchmode": string,
"bindaddress": string,
"bindinterface": string,
"canonicaldomains": [
string
],
"canonicalizefallbacklocal": string,
"canonicalizehostname": string,
"canonicalizemaxdots": integer,
"canonicalizepermittedcnames": [
string
],
"casignaturealgorithms": [
string
],
"certificatefile": [
string
],
"checkhostip": string,
"ciphers": [
string
],
"clearallforwardings": string,
"compression": string,
"connectionattempts": integer,
"connecttimeout": integer,
"controlmaster": string,
"controlpath": string,
"controlpersist": string,
"dynamicforward": string,
"enableescapecommandline": string,
"enablesshkeysign": string,
"escapechar": string,
"exitonforwardfailure": string,
"fingerprinthash": string,
"forkafterauthentication": string,
"forwardagent": string,
"forwardx11": string,
"forwardx11timeout": integer,
"forwardx11trusted": string,
"gatewayports": string,
"globalknownhostsfile": [
string
],
"gssapiauthentication": string,
"gssapidelegatecredentials": string,
"hashknownhosts": string,
"hostbasedacceptedalgorithms": [
string
],
"hostbasedauthentication": string,
"hostkeyalgorithms": [
string
],
"hostkeyalias": string,
"hostname": string,
"identitiesonly": string,
"identityagent": string,
"identityfile": [
string
],
"ignoreunknown": string,
"include": [
string
],
"ipqos": [
string
],
"kbdinteractiveauthentication": string,
"kbdinteractivedevices": [
string
],
"kexalgorithms": [
string
],
"kexalgorithms_strategy": string,
"knownhostscommand": string,
"localcommand": string,
"localforward": [
string
],
"loglevel": string,
"logverbose": [
string
],
"macs": [
string
],
"macs_strategy": string,
"nohostauthenticationforlocalhost": string,
"numberofpasswordprompts": integer,
"passwordauthentication": string,
"permitlocalcommand": string,
"permitremoteopen": [
string
],
"pkcs11provider": string,
"port": integer,
"preferredauthentications": [
string
],
"protocol": integer,
"proxycommand": string,
"proxyjump": [
string
],
"proxyusefdpass": string,
"pubkeyacceptedalgorithms": [
string
],
"pubkeyacceptedalgorithms_strategy": string,
"pubkeyauthentication": string,
"rekeylimit": string,
"remotecommand": string,
"remoteforward": string,
"requesttty": string,
"requiredrsasize": integer,
"revokedhostkeys": string,
"securitykeyprovider": string,
"sendenv": [
string
],
"serveralivecountmax": integer,
"serveraliveinterval": integer,
"sessiontype": string,
"setenv": [
string
],
"stdinnull": string,
"streamlocalbindmask": string,
"streamlocalbindunlink": string,
"stricthostkeychecking": string,
"syslogfacility": string,
"tcpkeepalive": string,
"tunnel": string,
"tunneldevice": string,
"updatehostkeys": string,
"user": string,
"userknownhostsfile": [
string
],
"verifyhostkeydns": string,
"visualhostkey": string,
"xauthlocation": string
}
]
Examples:
$ ssh -G - | jc --ssh-conf -p
[
{
"user": "foo",
"hostname": "-",
"port": 22,
"addressfamily": "any",
"batchmode": "no",
"canonicalizefallbacklocal": "yes",
"canonicalizehostname": "false",
"checkhostip": "no",
"compression": "no",
"controlmaster": "false",
"enablesshkeysign": "no",
"clearallforwardings": "no",
"exitonforwardfailure": "no",
"fingerprinthash": "SHA256",
"forwardx11": "no",
"forwardx11trusted": "no",
"gatewayports": "no",
"gssapiauthentication": "no",
"gssapidelegatecredentials": "no",
"hashknownhosts": "no",
"hostbasedauthentication": "no",
"identitiesonly": "no",
"kbdinteractiveauthentication": "yes",
"nohostauthenticationforlocalhost": "no",
"passwordauthentication": "yes",
"permitlocalcommand": "no",
"proxyusefdpass": "no",
"pubkeyauthentication": "true",
"requesttty": "auto",
"sessiontype": "default",
"stdinnull": "no",
"forkafterauthentication": "no",
"streamlocalbindunlink": "no",
"stricthostkeychecking": "ask",
"tcpkeepalive": "yes",
"tunnel": "false",
"verifyhostkeydns": "false",
"visualhostkey": "no",
"updatehostkeys": "true",
"applemultipath": "no",
"canonicalizemaxdots": 1,
"connectionattempts": 1,
"forwardx11timeout": 1200,
"numberofpasswordprompts": 3,
"serveralivecountmax": 3,
"serveraliveinterval": 0,
"ciphers": [
"chacha20-poly1305@openssh.com",
"aes128-ctr",
"aes192-ctr",
"aes256-ctr",
"aes128-gcm@openssh.com",
"aes256-gcm@openssh.com"
],
"hostkeyalgorithms": [
"ssh-ed25519-cert-v01@openssh.com",
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
"ecdsa-sha2-nistp521-cert-v01@openssh.com",
"rsa-sha2-512-cert-v01@openssh.com",
"rsa-sha2-256-cert-v01@openssh.com",
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"rsa-sha2-512",
"rsa-sha2-256"
],
"hostbasedacceptedalgorithms": [
"ssh-ed25519-cert-v01@openssh.com",
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
"ecdsa-sha2-nistp521-cert-v01@openssh.com",
"rsa-sha2-512-cert-v01@openssh.com",
"rsa-sha2-256-cert-v01@openssh.com",
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"rsa-sha2-512",
"rsa-sha2-256"
],
"kexalgorithms": [
"sntrup761x25519-sha512@openssh.com",
"curve25519-sha256",
"curve25519-sha256@libssh.org",
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group16-sha512",
"diffie-hellman-group18-sha512",
"diffie-hellman-group14-sha256"
],
"casignaturealgorithms": [
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"rsa-sha2-512",
"rsa-sha2-256"
],
"loglevel": "INFO",
"macs": [
"umac-64-etm@openssh.com",
"umac-128-etm@openssh.com",
"hmac-sha2-256-etm@openssh.com",
"hmac-sha2-512-etm@openssh.com",
"hmac-sha1-etm@openssh.com",
"umac-64@openssh.com",
"umac-128@openssh.com",
"hmac-sha2-256",
"hmac-sha2-512",
"hmac-sha1"
],
"securitykeyprovider": "$SSH_SK_PROVIDER",
"pubkeyacceptedalgorithms": [
"ssh-ed25519-cert-v01@openssh.com",
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
"ecdsa-sha2-nistp521-cert-v01@openssh.com",
"rsa-sha2-512-cert-v01@openssh.com",
"rsa-sha2-256-cert-v01@openssh.com",
"ssh-ed25519",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"rsa-sha2-512",
"rsa-sha2-256"
],
"xauthlocation": "/usr/X11R6/bin/xauth",
"identityfile": [
"~/.ssh/id_rsa",
"~/.ssh/id_ecdsa",
"~/.ssh/id_ecdsa_sk",
"~/.ssh/id_ed25519",
"~/.ssh/id_ed25519_sk",
"~/.ssh/id_xmss",
"~/.ssh/id_dsa"
],
"canonicaldomains": [
"none"
],
"globalknownhostsfile": [
"/etc/ssh/ssh_known_hosts",
"/etc/ssh/ssh_known_hosts2"
],
"userknownhostsfile": [
"/Users/foo/.ssh/known_hosts",
"/Users/foo/.ssh/known_hosts2"
],
"sendenv": [
"LANG",
"LC_*"
],
"logverbose": [
"none"
],
"permitremoteopen": [
"any"
],
"addkeystoagent": "false",
"forwardagent": "no",
"connecttimeout": null,
"tunneldevice": "any:any",
"canonicalizepermittedcnames": [
"none"
],
"controlpersist": "no",
"escapechar": "~",
"ipqos": [
"af21",
"cs1"
],
"rekeylimit": "0 0",
"streamlocalbindmask": "0177",
"syslogfacility": "USER"
}
]
$ cat ~/.ssh/config | jc --ssh-conf -p
[
{
"host": "server1",
"host_list": [
"server1"
],
"hostname": "server1.cyberciti.biz",
"user": "nixcraft",
"port": 4242,
"identityfile": [
"/nfs/shared/users/nixcraft/keys/server1/id_rsa"
]
},
{
"host": "nas01",
"host_list": [
"nas01"
],
"hostname": "192.168.1.100",
"user": "root",
"identityfile": [
"~/.ssh/nas01.key"
]
},
{
"host": "aws.apache",
"host_list": [
"aws.apache"
],
"hostname": "1.2.3.4",
"user": "wwwdata",
"identityfile": [
"~/.ssh/aws.apache.key"
]
},
{
"host": "uk.gw.lan uk.lan",
"host_list": [
"uk.gw.lan",
"uk.lan"
],
"hostname": "192.168.0.251",
"user": "nixcraft",
"proxycommand": "ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null"
},
{
"host": "proxyus",
"host_list": [
"proxyus"
],
"hostname": "vps1.cyberciti.biz",
"user": "breakfree",
"identityfile": [
"~/.ssh/vps1.cyberciti.biz.key"
],
"localforward": [
"3128 127.0.0.1:3128"
]
},
{
"host": "*",
"host_list": [
"*"
],
"forwardagent": "no",
"forwardx11": "no",
"forwardx11trusted": "yes",
"user": "nixcraft",
"port": 22,
"protocol": 2,
"serveraliveinterval": 60,
"serveralivecountmax": 30
}
]
$ cat ~/.ssh/config | jc --ssh-conf -p -r
[
{
"host": "server1",
"host_list": [
"server1"
],
"hostname": "server1.cyberciti.biz",
"user": "nixcraft",
"port": "4242",
"identityfile": [
"/nfs/shared/users/nixcraft/keys/server1/id_rsa"
]
},
{
"host": "nas01",
"host_list": [
"nas01"
],
"hostname": "192.168.1.100",
"user": "root",
"identityfile": [
"~/.ssh/nas01.key"
]
},
{
"host": "aws.apache",
"host_list": [
"aws.apache"
],
"hostname": "1.2.3.4",
"user": "wwwdata",
"identityfile": [
"~/.ssh/aws.apache.key"
]
},
{
"host": "uk.gw.lan uk.lan",
"host_list": [
"uk.gw.lan",
"uk.lan"
],
"hostname": "192.168.0.251",
"user": "nixcraft",
"proxycommand": "ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null"
},
{
"host": "proxyus",
"host_list": [
"proxyus"
],
"hostname": "vps1.cyberciti.biz",
"user": "breakfree",
"identityfile": [
"~/.ssh/vps1.cyberciti.biz.key"
],
"localforward": [
"3128 127.0.0.1:3128"
]
},
{
"host": "*",
"host_list": [
"*"
],
"forwardagent": "no",
"forwardx11": "no",
"forwardx11trusted": "yes",
"user": "nixcraft",
"port": "22",
"protocol": "2",
"serveraliveinterval": "60",
"serveralivecountmax": "30"
}
]
"""
from typing import Set, List, Dict
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`ssh` config file and `ssh -G` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'freebsd']
magic_commands = ['ssh -G']
tags = ['command', 'file']
__version__ = info.version
def _process(proc_data: List[JSONDictType]) -> List[JSONDictType]:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
List of Dictionaries. Structured to conform to the schema.
"""
split_fields_space: Set[str] = {
'canonicaldomains', 'globalknownhostsfile', 'include', 'ipqos',
'permitremoteopen', 'sendenv', 'setenv', 'userknownhostsfile'
}
split_fields_comma: Set[str] = {
'canonicalizepermittedcnames', 'casignaturealgorithms', 'ciphers',
'hostbasedacceptedalgorithms', 'hostkeyalgorithms',
'kbdinteractivedevices', 'kexalgorithms', 'logverbose', 'macs',
'preferredauthentications', 'proxyjump', 'pubkeyacceptedalgorithms'
}
int_list: Set[str] = {
'canonicalizemaxdots', 'connectionattempts', 'connecttimeout',
'forwardx11timeout', 'numberofpasswordprompts', 'port', 'protocol',
'requiredrsasize', 'serveralivecountmax', 'serveraliveinterval'
}
for host in proc_data:
dict_copy = host.copy()
for key, val in dict_copy.items():
# these are list values
if key == 'sendenv' or key == 'setenv' or key == 'include':
new_list: List[str] = []
for item in val:
new_list.extend(item.split())
host[key] = new_list
continue
if key in split_fields_space:
host[key] = val.split()
continue
if key in split_fields_comma:
host[key] = val.split(',')
continue
for key, val in host.items():
if key in int_list:
host[key] = jc.utils.convert_to_int(val)
return proc_data
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> List[JSONDictType]:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List = []
host: Dict = {}
multi_fields: Set[str] = {
'certificatefile', 'identityfile', 'include', 'localforward',
'sendenv', 'setenv'
}
modified_fields: Set[str] = {
'casignaturealgorithms', 'ciphers', 'hostbasedacceptedalgorithms',
'HostKeyAlgorithms', 'kexalgorithms', 'macs',
'pubkeyacceptedalgorithms'
}
modifiers: Set[str] = {'+', '-', '^'}
match_block_found = False
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
# skip any lines with only whitespace
if not line.strip():
continue
# support configuration file by skipping commented lines
if line.strip().startswith('#'):
continue
if line.strip().startswith('Host '):
if host:
raw_output.append(host)
hostnames = line.split(maxsplit=1)[1]
host = {
'host': hostnames,
'host_list': hostnames.split()
}
# support configuration file by ignoring all lines between
# Match xxx and Match any
if line.strip().startswith('Match all'):
match_block_found = False
continue
if line.strip().startswith('Match'):
match_block_found = True
continue
if match_block_found:
continue
key, val = line.split(maxsplit=1)
# support configuration file by converting to lower case
key = key.lower()
if key in multi_fields:
if key not in host:
host[key] = []
host[key].append(val)
continue
if key in modified_fields and val[0] in modifiers:
host[key] = val[1:]
host[key + '_strategy'] = val[0]
continue
host[key] = val
continue
if host:
raw_output.append(host)
return raw_output if raw else _process(raw_output)

View File

@@ -1,4 +1,4 @@
"""jc - JSON Convert sshd configuration file and `sshd -T` command output parser
"""jc - JSON Convert `sshd` configuration file and `sshd -T` command output parser
This parser will work with `sshd` configuration files or the output of
`sshd -T`. Any `Match` blocks in the `sshd` configuration file will be
@@ -483,13 +483,13 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = 'sshd config file and `sshd -T` command parser'
version = '1.1'
description = '`sshd` config file and `sshd -T` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'freebsd']
magic_commands = ['sshd -T']
tags = ['file']
tags = ['command', 'file']
__version__ = info.version
@@ -622,6 +622,10 @@ def parse(
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
# skip any lines with only whitespace
if not line.strip():
continue
# support configuration file by skipping commented lines
if line.strip().startswith('#'):
continue

149
jc/parsers/toml.py Normal file
View File

@@ -0,0 +1,149 @@
"""jc - JSON Convert TOML file parser
Usage (cli):
$ cat file.toml | jc --toml
Usage (module):
import jc
result = jc.parse('toml', toml_file_output)
Schema:
TOML Document converted to a Dictionary.
See https://toml.io/en/ for details.
{
"key1": string/int/float/boolean/null/array/object,
"key2": string/int/float/boolean/null/array/object
}
Examples:
$ cat file.toml
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
enabled = true
ports = [ 8000, 8001, 8002 ]
$ cat file.toml | jc --toml -p
{
"title": "TOML Example",
"owner": {
"name": "Tom Preston-Werner",
"dob": 296667120,
"dob_iso": "1979-05-27T07:32:00-08:00"
},
"database": {
"enabled": true,
"ports": [
8000,
8001,
8002
]
}
}
"""
from typing import Any
from jc.jc_types import JSONDictType
import jc.utils
from jc.parsers import tomli
from datetime import datetime
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = 'TOML file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
details = 'Using the tomli library at https://github.com/hukkin/tomli.'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['standard', 'file', 'string']
__version__ = info.version
def _process(proc_data: JSONDictType) -> JSONDictType:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
Dictionary. Structured to conform to the schema.
"""
return proc_data
def _fix_objects(obj: Any) -> JSONDictType:
"""
Recursively traverse the nested dictionary or list and convert objects
into JSON serializable types.
"""
if isinstance(obj, dict):
for k, v in obj.copy().items():
if isinstance(v, datetime):
iso = v.isoformat()
v = int(round(v.timestamp()))
obj.update({k: v, f'{k}_iso': iso})
continue
if isinstance(v, dict):
obj.update({k: _fix_objects(v)})
continue
if isinstance(v, list):
newlist = []
for i in v:
newlist.append(_fix_objects(i))
obj.update({k: newlist})
continue
if isinstance(obj, list):
new_list = []
for i in obj:
new_list.append(_fix_objects(i))
obj = new_list
return obj
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> JSONDictType:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
Dictionary. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: JSONDictType = {}
if jc.utils.has_data(data):
raw_output = _fix_objects(tomli.loads(data))
return raw_output if raw else _process(raw_output)

21
jc/parsers/tomli/LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Taneli Hukkinen
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.

View File

@@ -0,0 +1,11 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
# Licensed to PSF under a Contributor Agreement.
__all__ = ("loads", "load", "TOMLDecodeError")
__version__ = "2.0.1" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT
from ._parser import TOMLDecodeError, load, loads
# Pretend this exception was created here.
TOMLDecodeError.__module__ = __name__

691
jc/parsers/tomli/_parser.py Normal file
View File

@@ -0,0 +1,691 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
# Licensed to PSF under a Contributor Agreement.
# from __future__ import annotations
from collections import namedtuple
import string
# from types import MappingProxyType
# from typing import Any, BinaryIO, NamedTuple
from ._re import (
RE_DATETIME,
RE_LOCALTIME,
RE_NUMBER,
match_to_datetime,
match_to_localtime,
match_to_number,
)
# from ._types import Key, ParseFloat, Pos
ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
# Neither of these sets include quotation mark or backslash. They are
# currently handled as separate cases in the parser functions.
ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t")
ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset("\t\n")
ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS
ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ILLEGAL_MULTILINE_BASIC_STR_CHARS
ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS
TOML_WS = frozenset(" \t")
TOML_WS_AND_NEWLINE = TOML_WS | frozenset("\n")
BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + "-_")
KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'")
HEXDIGIT_CHARS = frozenset(string.hexdigits)
BASIC_STR_ESCAPE_REPLACEMENTS = {
"\\b": "\u0008", # backspace
"\\t": "\u0009", # tab
"\\n": "\u000A", # linefeed
"\\f": "\u000C", # form feed
"\\r": "\u000D", # carriage return
'\\"': "\u0022", # quote
"\\\\": "\u005C", # backslash
}
class TOMLDecodeError(ValueError):
"""An error raised if a document is not valid TOML."""
def load(__fp, *, parse_float=float):
"""Parse TOML from a binary file object."""
b = __fp.read()
try:
s = b.decode()
except AttributeError:
raise TypeError(
"File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`"
) from None
return loads(s, parse_float=parse_float)
def loads(__s, *, parse_float=float):
"""Parse TOML from a string."""
# The spec allows converting "\r\n" to "\n", even in string
# literals. Let's do so to simplify parsing.
src = __s.replace("\r\n", "\n")
pos = 0
out = Output(NestedDict(), Flags())
header = ()
parse_float = make_safe_parse_float(parse_float)
# Parse one statement at a time
# (typically means one line in TOML source)
while True:
# 1. Skip line leading whitespace
pos = skip_chars(src, pos, TOML_WS)
# 2. Parse rules. Expect one of the following:
# - end of file
# - end of line
# - comment
# - key/value pair
# - append dict to list (and move to its namespace)
# - create dict (and move to its namespace)
# Skip trailing whitespace when applicable.
try:
char = src[pos]
except IndexError:
break
if char == "\n":
pos += 1
continue
if char in KEY_INITIAL_CHARS:
pos = key_value_rule(src, pos, out, header, parse_float)
pos = skip_chars(src, pos, TOML_WS)
elif char == "[":
try:
second_char = src[pos + 1]
except IndexError:
second_char = None
out.flags.finalize_pending()
if second_char == "[":
pos, header = create_list_rule(src, pos, out)
else:
pos, header = create_dict_rule(src, pos, out)
pos = skip_chars(src, pos, TOML_WS)
elif char != "#":
raise suffixed_err(src, pos, "Invalid statement")
# 3. Skip comment
pos = skip_comment(src, pos)
# 4. Expect end of line or end of file
try:
char = src[pos]
except IndexError:
break
if char != "\n":
raise suffixed_err(
src, pos, "Expected newline or end of document after a statement"
)
pos += 1
return out.data.dict
class Flags:
"""Flags that map to parsed keys/namespaces."""
# Marks an immutable namespace (inline array or inline table).
FROZEN = 0
# Marks a nest that has been explicitly created and can no longer
# be opened using the "[table]" syntax.
EXPLICIT_NEST = 1
def __init__(self):
self._flags = {}
self._pending_flags = set()
def add_pending(self, key, flag):
self._pending_flags.add((key, flag))
def finalize_pending(self):
for key, flag in self._pending_flags:
self.set(key, flag, recursive=False)
self._pending_flags.clear()
def unset_all(self, key):
cont = self._flags
for k in key[:-1]:
if k not in cont:
return
cont = cont[k]["nested"]
cont.pop(key[-1], None)
def set(self, key, flag, *, recursive):
cont = self._flags
key_parent, key_stem = key[:-1], key[-1]
for k in key_parent:
if k not in cont:
cont[k] = {"flags": set(), "recursive_flags": set(), "nested": {}}
cont = cont[k]["nested"]
if key_stem not in cont:
cont[key_stem] = {"flags": set(), "recursive_flags": set(), "nested": {}}
cont[key_stem]["recursive_flags" if recursive else "flags"].add(flag)
def is_(self, key, flag):
if not key:
return False # document root has no flags
cont = self._flags
for k in key[:-1]:
if k not in cont:
return False
inner_cont = cont[k]
if flag in inner_cont["recursive_flags"]:
return True
cont = inner_cont["nested"]
key_stem = key[-1]
if key_stem in cont:
cont = cont[key_stem]
return flag in cont["flags"] or flag in cont["recursive_flags"]
return False
class NestedDict:
def __init__(self):
# The parsed content of the TOML document
self.dict = {}
def get_or_create_nest(
self,
key,
*,
access_lists: bool = True,
) -> dict:
cont = self.dict
for k in key:
if k not in cont:
cont[k] = {}
cont = cont[k]
if access_lists and isinstance(cont, list):
cont = cont[-1]
if not isinstance(cont, dict):
raise KeyError("There is no nest behind this key")
return cont
def append_nest_to_list(self, key):
cont = self.get_or_create_nest(key[:-1])
last_key = key[-1]
if last_key in cont:
list_ = cont[last_key]
if not isinstance(list_, list):
raise KeyError("An object other than list found behind this key")
list_.append({})
else:
cont[last_key] = [{}]
# class Output(namedtuple):
# data: NestedDict
# flags: Flags
Output = namedtuple('Output', ['data', 'flags'])
def skip_chars(src: str, pos, chars):
try:
while src[pos] in chars:
pos += 1
except IndexError:
pass
return pos
def skip_until(
src,
pos,
expect,
*,
error_on,
error_on_eof,
):
try:
new_pos = src.index(expect, pos)
except ValueError:
new_pos = len(src)
if error_on_eof:
raise suffixed_err(src, new_pos, f"Expected {expect!r}") from None
if not error_on.isdisjoint(src[pos:new_pos]):
while src[pos] not in error_on:
pos += 1
raise suffixed_err(src, pos, f"Found invalid character {src[pos]!r}")
return new_pos
def skip_comment(src, pos):
try:
char = src[pos]
except IndexError:
char = None
if char == "#":
return skip_until(
src, pos + 1, "\n", error_on=ILLEGAL_COMMENT_CHARS, error_on_eof=False
)
return pos
def skip_comments_and_array_ws(src, pos):
while True:
pos_before_skip = pos
pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE)
pos = skip_comment(src, pos)
if pos == pos_before_skip:
return pos
def create_dict_rule(src, pos, out):
pos += 1 # Skip "["
pos = skip_chars(src, pos, TOML_WS)
pos, key = parse_key(src, pos)
if out.flags.is_(key, Flags.EXPLICIT_NEST) or out.flags.is_(key, Flags.FROZEN):
raise suffixed_err(src, pos, f"Cannot declare {key} twice")
out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
try:
out.data.get_or_create_nest(key)
except KeyError:
raise suffixed_err(src, pos, "Cannot overwrite a value") from None
if not src.startswith("]", pos):
raise suffixed_err(src, pos, "Expected ']' at the end of a table declaration")
return pos + 1, key
def create_list_rule(src: str, pos, out):
pos += 2 # Skip "[["
pos = skip_chars(src, pos, TOML_WS)
pos, key = parse_key(src, pos)
if out.flags.is_(key, Flags.FROZEN):
raise suffixed_err(src, pos, f"Cannot mutate immutable namespace {key}")
# Free the namespace now that it points to another empty list item...
out.flags.unset_all(key)
# ...but this key precisely is still prohibited from table declaration
out.flags.set(key, Flags.EXPLICIT_NEST, recursive=False)
try:
out.data.append_nest_to_list(key)
except KeyError:
raise suffixed_err(src, pos, "Cannot overwrite a value") from None
if not src.startswith("]]", pos):
raise suffixed_err(src, pos, "Expected ']]' at the end of an array declaration")
return pos + 2, key
def key_value_rule(
src, pos, out, header, parse_float
):
pos, key, value = parse_key_value_pair(src, pos, parse_float)
key_parent, key_stem = key[:-1], key[-1]
abs_key_parent = header + key_parent
relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
for cont_key in relative_path_cont_keys:
# Check that dotted key syntax does not redefine an existing table
if out.flags.is_(cont_key, Flags.EXPLICIT_NEST):
raise suffixed_err(src, pos, f"Cannot redefine namespace {cont_key}")
# Containers in the relative path can't be opened with the table syntax or
# dotted key/value syntax in following table sections.
out.flags.add_pending(cont_key, Flags.EXPLICIT_NEST)
if out.flags.is_(abs_key_parent, Flags.FROZEN):
raise suffixed_err(
src, pos, f"Cannot mutate immutable namespace {abs_key_parent}"
)
try:
nest = out.data.get_or_create_nest(abs_key_parent)
except KeyError:
raise suffixed_err(src, pos, "Cannot overwrite a value") from None
if key_stem in nest:
raise suffixed_err(src, pos, "Cannot overwrite a value")
# Mark inline table and array namespaces recursively immutable
if isinstance(value, (dict, list)):
out.flags.set(header + key, Flags.FROZEN, recursive=True)
nest[key_stem] = value
return pos
def parse_key_value_pair(
src, pos, parse_float
):
pos, key = parse_key(src, pos)
try:
char = src[pos]
except IndexError:
char = None
if char != "=":
raise suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")
pos += 1
pos = skip_chars(src, pos, TOML_WS)
pos, value = parse_value(src, pos, parse_float)
return pos, key, value
def parse_key(src, pos):
pos, key_part = parse_key_part(src, pos)
key = (key_part,)
pos = skip_chars(src, pos, TOML_WS)
while True:
try:
char = src[pos]
except IndexError:
char = None
if char != ".":
return pos, key
pos += 1
pos = skip_chars(src, pos, TOML_WS)
pos, key_part = parse_key_part(src, pos)
key += (key_part,)
pos = skip_chars(src, pos, TOML_WS)
def parse_key_part(src, pos):
try:
char = src[pos]
except IndexError:
char = None
if char in BARE_KEY_CHARS:
start_pos = pos
pos = skip_chars(src, pos, BARE_KEY_CHARS)
return pos, src[start_pos:pos]
if char == "'":
return parse_literal_str(src, pos)
if char == '"':
return parse_one_line_basic_str(src, pos)
raise suffixed_err(src, pos, "Invalid initial character for a key part")
def parse_one_line_basic_str(src, pos):
pos += 1
return parse_basic_str(src, pos, multiline=False)
def parse_array(src, pos, parse_float):
pos += 1
array = []
pos = skip_comments_and_array_ws(src, pos)
if src.startswith("]", pos):
return pos + 1, array
while True:
pos, val = parse_value(src, pos, parse_float)
array.append(val)
pos = skip_comments_and_array_ws(src, pos)
c = src[pos : pos + 1]
if c == "]":
return pos + 1, array
if c != ",":
raise suffixed_err(src, pos, "Unclosed array")
pos += 1
pos = skip_comments_and_array_ws(src, pos)
if src.startswith("]", pos):
return pos + 1, array
def parse_inline_table(src, pos, parse_float):
pos += 1
nested_dict = NestedDict()
flags = Flags()
pos = skip_chars(src, pos, TOML_WS)
if src.startswith("}", pos):
return pos + 1, nested_dict.dict
while True:
pos, key, value = parse_key_value_pair(src, pos, parse_float)
key_parent, key_stem = key[:-1], key[-1]
if flags.is_(key, Flags.FROZEN):
raise suffixed_err(src, pos, f"Cannot mutate immutable namespace {key}")
try:
nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
except KeyError:
raise suffixed_err(src, pos, "Cannot overwrite a value") from None
if key_stem in nest:
raise suffixed_err(src, pos, f"Duplicate inline table key {key_stem!r}")
nest[key_stem] = value
pos = skip_chars(src, pos, TOML_WS)
c = src[pos : pos + 1]
if c == "}":
return pos + 1, nested_dict.dict
if c != ",":
raise suffixed_err(src, pos, "Unclosed inline table")
if isinstance(value, (dict, list)):
flags.set(key, Flags.FROZEN, recursive=True)
pos += 1
pos = skip_chars(src, pos, TOML_WS)
def parse_basic_str_escape(
src, pos, *, multiline = False
):
escape_id = src[pos : pos + 2]
pos += 2
if multiline and escape_id in {"\\ ", "\\\t", "\\\n"}:
# Skip whitespace until next non-whitespace character or end of
# the doc. Error if non-whitespace is found before newline.
if escape_id != "\\\n":
pos = skip_chars(src, pos, TOML_WS)
try:
char = src[pos]
except IndexError:
return pos, ""
if char != "\n":
raise suffixed_err(src, pos, "Unescaped '\\' in a string")
pos += 1
pos = skip_chars(src, pos, TOML_WS_AND_NEWLINE)
return pos, ""
if escape_id == "\\u":
return parse_hex_char(src, pos, 4)
if escape_id == "\\U":
return parse_hex_char(src, pos, 8)
try:
return pos, BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
except KeyError:
raise suffixed_err(src, pos, "Unescaped '\\' in a string") from None
def parse_basic_str_escape_multiline(src, pos):
return parse_basic_str_escape(src, pos, multiline=True)
def parse_hex_char(src, pos, hex_len):
hex_str = src[pos : pos + hex_len]
if len(hex_str) != hex_len or not HEXDIGIT_CHARS.issuperset(hex_str):
raise suffixed_err(src, pos, "Invalid hex value")
pos += hex_len
hex_int = int(hex_str, 16)
if not is_unicode_scalar_value(hex_int):
raise suffixed_err(src, pos, "Escaped character is not a Unicode scalar value")
return pos, chr(hex_int)
def parse_literal_str(src, pos):
pos += 1 # Skip starting apostrophe
start_pos = pos
pos = skip_until(
src, pos, "'", error_on=ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True
)
return pos + 1, src[start_pos:pos] # Skip ending apostrophe
def parse_multiline_str(src, pos, *, literal):
pos += 3
if src.startswith("\n", pos):
pos += 1
if literal:
delim = "'"
end_pos = skip_until(
src,
pos,
"'''",
error_on=ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
error_on_eof=True,
)
result = src[pos:end_pos]
pos = end_pos + 3
else:
delim = '"'
pos, result = parse_basic_str(src, pos, multiline=True)
# Add at maximum two extra apostrophes/quotes if the end sequence
# is 4 or 5 chars long instead of just 3.
if not src.startswith(delim, pos):
return pos, result
pos += 1
if not src.startswith(delim, pos):
return pos, result + delim
pos += 1
return pos, result + (delim * 2)
def parse_basic_str(src, pos, *, multiline):
if multiline:
error_on = ILLEGAL_MULTILINE_BASIC_STR_CHARS
parse_escapes = parse_basic_str_escape_multiline
else:
error_on = ILLEGAL_BASIC_STR_CHARS
parse_escapes = parse_basic_str_escape
result = ""
start_pos = pos
while True:
try:
char = src[pos]
except IndexError:
raise suffixed_err(src, pos, "Unterminated string") from None
if char == '"':
if not multiline:
return pos + 1, result + src[start_pos:pos]
if src.startswith('"""', pos):
return pos + 3, result + src[start_pos:pos]
pos += 1
continue
if char == "\\":
result += src[start_pos:pos]
pos, parsed_escape = parse_escapes(src, pos)
result += parsed_escape
start_pos = pos
continue
if char in error_on:
raise suffixed_err(src, pos, f"Illegal character {char!r}")
pos += 1
def parse_value( # noqa: C901
src, pos, parse_float
):
try:
char = src[pos]
except IndexError:
char = None
# IMPORTANT: order conditions based on speed of checking and likelihood
# Basic strings
if char == '"':
if src.startswith('"""', pos):
return parse_multiline_str(src, pos, literal=False)
return parse_one_line_basic_str(src, pos)
# Literal strings
if char == "'":
if src.startswith("'''", pos):
return parse_multiline_str(src, pos, literal=True)
return parse_literal_str(src, pos)
# Booleans
if char == "t":
if src.startswith("true", pos):
return pos + 4, True
if char == "f":
if src.startswith("false", pos):
return pos + 5, False
# Arrays
if char == "[":
return parse_array(src, pos, parse_float)
# Inline tables
if char == "{":
return parse_inline_table(src, pos, parse_float)
# Dates and times
datetime_match = RE_DATETIME.match(src, pos)
if datetime_match:
try:
datetime_obj = match_to_datetime(datetime_match)
except ValueError as e:
raise suffixed_err(src, pos, "Invalid date or datetime") from e
return datetime_match.end(), datetime_obj
localtime_match = RE_LOCALTIME.match(src, pos)
if localtime_match:
return localtime_match.end(), match_to_localtime(localtime_match)
# Integers and "normal" floats.
# The regex will greedily match any type starting with a decimal
# char, so needs to be located after handling of dates and times.
number_match = RE_NUMBER.match(src, pos)
if number_match:
return number_match.end(), match_to_number(number_match, parse_float)
# Special floats
first_three = src[pos : pos + 3]
if first_three in {"inf", "nan"}:
return pos + 3, parse_float(first_three)
first_four = src[pos : pos + 4]
if first_four in {"-inf", "+inf", "-nan", "+nan"}:
return pos + 4, parse_float(first_four)
raise suffixed_err(src, pos, "Invalid value")
def suffixed_err(src, pos, msg):
"""Return a `TOMLDecodeError` where error message is suffixed with
coordinates in source."""
def coord_repr(src, pos):
if pos >= len(src):
return "end of document"
line = src.count("\n", 0, pos) + 1
if line == 1:
column = pos + 1
else:
column = pos - src.rindex("\n", 0, pos)
return f"line {line}, column {column}"
return TOMLDecodeError(f"{msg} (at {coord_repr(src, pos)})")
def is_unicode_scalar_value(codepoint: int) -> bool:
return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
def make_safe_parse_float(parse_float):
"""A decorator to make `parse_float` safe.
`parse_float` must not return dicts or lists, because these types
would be mixed with parsed TOML tables and arrays, thus confusing
the parser. The returned decorated callable raises `ValueError`
instead of returning illegal types.
"""
# The default `float` callable never returns illegal types. Optimize it.
if parse_float is float:
return float
def safe_parse_float(float_str):
float_value = parse_float(float_str)
if isinstance(float_value, (dict, list)):
raise ValueError("parse_float must not return dicts or lists")
return float_value
return safe_parse_float

101
jc/parsers/tomli/_re.py Normal file
View File

@@ -0,0 +1,101 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
# Licensed to PSF under a Contributor Agreement.
from datetime import date, datetime, time, timedelta, timezone, tzinfo
from functools import lru_cache
import re
# E.g.
# - 00:32:00.999999
# - 00:32:00
_TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?"
RE_NUMBER = re.compile(
r"""
0
(?:
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
b[01](?:_?[01])* # bin
|
o[0-7](?:_?[0-7])* # oct
)
|
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
(?P<floatpart>
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
)
""",
flags=re.VERBOSE,
)
RE_LOCALTIME = re.compile(_TIME_RE_STR)
RE_DATETIME = re.compile(
rf"""
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
(?:
[Tt ]
{_TIME_RE_STR}
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
)?
""",
flags=re.VERBOSE,
)
def match_to_datetime(match):
"""Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
Raises ValueError if the match does not correspond to a valid date
or datetime.
"""
(
year_str,
month_str,
day_str,
hour_str,
minute_str,
sec_str,
micros_str,
zulu_time,
offset_sign_str,
offset_hour_str,
offset_minute_str,
) = match.groups()
year, month, day = int(year_str), int(month_str), int(day_str)
if hour_str is None:
return date(year, month, day)
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
micros = int(micros_str.ljust(6, "0")) if micros_str else 0
if offset_sign_str:
tz = cached_tz(
offset_hour_str, offset_minute_str, offset_sign_str
)
elif zulu_time:
tz = timezone.utc
else: # local date-time
tz = None
return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
@lru_cache(maxsize=None)
def cached_tz(hour_str, minute_str, sign_str):
sign = 1 if sign_str == "+" else -1
return timezone(
timedelta(
hours=sign * int(hour_str),
minutes=sign * int(minute_str),
)
)
def match_to_localtime(match):
hour_str, minute_str, sec_str, micros_str = match.groups()
micros = int(micros_str.ljust(6, "0")) if micros_str else 0
return time(int(hour_str), int(minute_str), int(sec_str), micros)
def match_to_number(match, parse_float):
if match.group("floatpart"):
return parse_float(match.group())
return int(match.group(), 0)

View File

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
# Licensed to PSF under a Contributor Agreement.
# Type annotations
# ParseFloat = Callable[[str], Any]
# Key = Tuple[str, ...]
# Pos = int

View File

@@ -0,0 +1 @@
# Marker file for PEP 561

View File

@@ -28,8 +28,7 @@ def simple_table_parse(data: Iterable[str]) -> List[Dict]:
underscore '_'. You should also ensure headers are
lowercase by using .lower().
Also, ensure there are no blank lines (list items)
in the data.
Also, ensure there are no blank rows in the data.
Returns:

209
jc/parsers/ver.py Normal file
View File

@@ -0,0 +1,209 @@
"""jc - JSON Convert Version string output parser
Best-effort attempt to parse various styles of version numbers. This parser
is based off of the version parser included in the CPython distutils
libary.
If the version string conforms to some de facto-standard versioning rules
followed by many developers a `strict` key will be present in the output
with a value of `true` along with the named parsed components.
All other version strings will have a `strict` value of `false` and a
`components` key will contain a list of detected parts of the version
string.
See Also: `semver` parser.
Usage (cli):
$ echo 1.2a1 | jc --ver
Usage (module):
import jc
result = jc.parse('ver', version_string_output)
Schema:
{
"major": integer,
"minor": integer,
"patch": integer,
"prerelease": string,
"prerelease_num": integer,
"components": [
integer/string
],
"strict": boolean
}
Examples:
$ echo 1.2a1 | jc --ver -p
{
"major": 1,
"minor": 2,
"patch": 0,
"prerelease": "a",
"prerelease_num": 1,
"strict": true
}
$ echo 1.2a1 | jc --ver -p -r
{
"major": "1",
"minor": "2",
"patch": "0",
"prerelease": "a",
"prerelease_num": "1",
"strict": true
}
$ echo 1.2beta3 | jc --ver -p
{
"components": [
1,
2,
"beta",
3
],
"strict": false
}
$ echo 1.2beta3 | jc --ver -p -r
{
"components": [
"1",
"2",
"beta",
"3"
],
"strict": false
}
"""
import re
from typing import Dict
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = 'Version string parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
details = 'Based on distutils/version.py from CPython 3.9.5.'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
tags = ['generic', 'string']
__version__ = info.version
def _process(proc_data: JSONDictType) -> JSONDictType:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
List of Dictionaries. Structured to conform to the schema.
"""
int_list = {'major', 'minor', 'patch', 'prerelease', 'prerelease_num'}
for k, v in proc_data.items():
if k in int_list:
try:
proc_data[k] = int(v)
except Exception:
pass
if 'components' in proc_data:
for i, obj in enumerate(proc_data['components']):
try:
proc_data['components'][i] = int(obj)
except Exception:
pass
return proc_data
def strict_parse(vstring):
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$', re.VERBOSE)
match = version_re.match(vstring)
if not match:
raise ValueError("invalid version number '%s'" % vstring)
(major, minor, patch, prerelease, prerelease_num) = \
match.group(1, 2, 4, 5, 6)
if not patch:
patch = '0'
if prerelease:
prerelease = prerelease[0]
else:
prerelease = None
return {
'major': major,
'minor': minor,
'patch': patch,
'prerelease': prerelease,
'prerelease_num': prerelease_num
}
def loose_parse(vstring):
component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
components = [x for x in component_re.split(vstring) if x and x != '.']
return components
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> JSONDictType:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: Dict = {}
strict = True
if jc.utils.has_data(data):
# based on distutils/version.py from CPython 3.9.5
# PSF License (see https://opensource.org/licenses/Python-2.0)
data = data.strip()
try:
raw_output = strict_parse(data)
except ValueError:
raw_output['components'] = loose_parse(data)
strict = False
if raw_output:
raw_output['strict'] = strict
return raw_output if raw else _process(raw_output)

View File

@@ -3,6 +3,7 @@
Usage (cli):
$ xrandr | jc --xrandr
$ xrandr --properties | jc --xrandr
or
@@ -44,13 +45,17 @@ Schema:
"is_connected": boolean,
"is_primary": boolean,
"device_name": string,
"model_name": string,
"product_id" string,
"serial_number": string,
"resolution_width": integer,
"resolution_height": integer,
"offset_width": integer,
"offset_height": integer,
"dimension_width": integer,
"dimension_height": integer,
"rotation": string
"rotation": string,
"reflection": string
}
],
"unassociated_devices": [
@@ -127,7 +132,71 @@ Examples:
"offset_height": 0,
"dimension_width": 310,
"dimension_height": 170,
"rotation": "normal"
"rotation": "normal",
"reflection": "normal"
}
}
],
"unassociated_devices": []
}
$ xrandr --properties | jc --xrandr -p
{
"screens": [
{
"screen_number": 0,
"minimum_width": 8,
"minimum_height": 8,
"current_width": 1920,
"current_height": 1080,
"maximum_width": 32767,
"maximum_height": 32767,
"associated_device": {
"associated_modes": [
{
"resolution_width": 1920,
"resolution_height": 1080,
"is_high_resolution": false,
"frequencies": [
{
"frequency": 60.03,
"is_current": true,
"is_preferred": true
},
{
"frequency": 59.93,
"is_current": false,
"is_preferred": false
}
]
},
{
"resolution_width": 1680,
"resolution_height": 1050,
"is_high_resolution": false,
"frequencies": [
{
"frequency": 59.88,
"is_current": false,
"is_preferred": false
}
]
}
],
"is_connected": true,
"is_primary": true,
"device_name": "eDP1",
"model_name": "ASUS VW193S",
"product_id": "54297",
"serial_number": "78L8021107",
"resolution_width": 1920,
"resolution_height": 1080,
"offset_width": 0,
"offset_height": 0,
"dimension_width": 310,
"dimension_height": 170,
"rotation": "normal",
"reflection": "normal"
}
}
],
@@ -137,14 +206,17 @@ Examples:
import re
from typing import Dict, List, Optional, Union
import jc.utils
from jc.parsers.pyedid.edid import Edid
from jc.parsers.pyedid.helpers.edid_helper import EdidHelper
class info:
"""Provides parser metadata (version, author, etc.)"""
version = "1.1"
version = "1.2"
description = "`xrandr` command parser"
author = "Kevin Lyter"
author_email = "lyter_git at sent.com"
details = 'Using parts of the pyedid library at https://github.com/jojonas/pyedid.'
compatible = ["linux", "darwin", "cygwin", "aix", "freebsd"]
magic_commands = ["xrandr"]
tags = ['command']
@@ -172,10 +244,21 @@ try:
"frequencies": List[Frequency],
},
)
Model = TypedDict(
"Model",
{
"name": str,
"product_id": str,
"serial_number": str,
},
)
Device = TypedDict(
"Device",
{
"device_name": str,
"model_name": str,
"product_id": str,
"serial_number": str,
"is_connected": bool,
"is_primary": bool,
"resolution_width": int,
@@ -185,6 +268,8 @@ try:
"dimension_width": int,
"dimension_height": int,
"associated_modes": List[Mode],
"rotation": str,
"reflection": str,
},
)
Screen = TypedDict(
@@ -212,6 +297,7 @@ except ImportError:
Device = Dict[str, Union[str, int, bool]]
Frequency = Dict[str, Union[float, bool]]
Mode = Dict[str, Union[int, bool, List[Frequency]]]
Model = Dict[str, str]
Response = Dict[str, Union[Device, Mode, Screen]]
@@ -252,7 +338,8 @@ _device_pattern = (
+ r"(?P<is_primary> primary)? ?"
+ r"((?P<resolution_width>\d+)x(?P<resolution_height>\d+)"
+ r"\+(?P<offset_width>\d+)\+(?P<offset_height>\d+))? "
+ r"(?P<rotation>(inverted|left|right))? ?"
+ r"(?P<rotation>(normal|right|left|inverted)?) ?"
+ r"(?P<reflection>(X axis|Y axis|X and Y axis)?) ?"
+ r"\(normal left inverted right x axis y axis\)"
+ r"( ((?P<dimension_width>\d+)mm x (?P<dimension_height>\d+)mm)?)?"
)
@@ -277,9 +364,10 @@ def _parse_device(next_lines: List[str], quiet: bool = False) -> Optional[Device
and len(matches["is_primary"]) > 0,
"device_name": matches["device_name"],
"rotation": matches["rotation"] or "normal",
"reflection": matches["reflection"] or "normal",
}
for k, v in matches.items():
if k not in {"is_connected", "is_primary", "device_name", "rotation"}:
if k not in {"is_connected", "is_primary", "device_name", "rotation", "reflection"}:
try:
if v:
device[k] = int(v)
@@ -288,15 +376,67 @@ def _parse_device(next_lines: List[str], quiet: bool = False) -> Optional[Device
[f"{next_line} : {k} - {v} is not int-able"]
)
model: Optional[Model] = _parse_model(next_lines, quiet)
if model:
device["model_name"] = model["name"]
device["product_id"] = model["product_id"]
device["serial_number"] = model["serial_number"]
while next_lines:
next_line = next_lines.pop()
next_mode: Optional[Mode] = _parse_mode(next_line)
if next_mode:
device["associated_modes"].append(next_mode)
else:
if re.match(_device_pattern, next_line):
next_lines.append(next_line)
break
return device
# EDID:
# 00ffffffffffff004ca3523100000000
# 0014010380221378eac8959e57549226
# 0f505400000001010101010101010101
# 010101010101381d56d4500016303020
# 250058c2100000190000000f00000000
# 000000000025d9066a00000000fe0053
# 414d53554e470a204ca34154000000fe
# 004c544e313536415432343430310018
_edid_head_pattern = r"\s*EDID:\s*"
_edid_line_pattern = r"\s*(?P<edid_line>[0-9a-fA-F]{32})\s*"
def _parse_model(next_lines: List[str], quiet: bool = False) -> Optional[Model]:
if not next_lines:
return None
next_line = next_lines.pop()
if not re.match(_edid_head_pattern, next_line):
next_lines.append(next_line)
return None
edid_hex_value = ""
while next_lines:
next_line = next_lines.pop()
result = re.match(_edid_line_pattern, next_line)
if not result:
next_lines.append(next_line)
break
return device
matches = result.groupdict()
edid_hex_value += matches["edid_line"]
edid = Edid(EdidHelper.hex2bytes(edid_hex_value))
model: Model = {
"name": edid.name or "Generic",
"product_id": str(edid.product),
"serial_number": str(edid.serial),
}
return model
# 1920x1080i 60.03*+ 59.93
@@ -330,8 +470,8 @@ def _parse_mode(line: str) -> Optional[Mode]:
for match in result:
d = match.groupdict()
frequency = float(d["frequency"])
is_current = len(d["star"]) > 0
is_preferred = len(d["plus"]) > 0
is_current = len(d["star"].strip()) > 0
is_preferred = len(d["plus"].strip()) > 0
f: Frequency = {
"frequency": frequency,
"is_current": is_current,

View File

@@ -13,8 +13,8 @@ Usage (module):
Schema:
YAML Document converted to a Dictionary
See https://pypi.org/project/ruamel.yaml for details
YAML Document converted to a Dictionary.
See https://pypi.org/project/ruamel.yaml for details.
[
{
@@ -25,7 +25,7 @@ Schema:
Examples:
$ cat istio-mtls-permissive.yaml
$ cat file.yaml
apiVersion: "authentication.istio.io/v1alpha1"
kind: "Policy"
metadata:
@@ -46,7 +46,7 @@ Examples:
tls:
mode: ISTIO_MUTUAL
$ cat istio-mtls-permissive.yaml | jc --yaml -p
$ cat file.yaml | jc --yaml -p
[
{
"apiVersion": "authentication.istio.io/v1alpha1",

View File

@@ -77,7 +77,7 @@ import jc.parsers.universal
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.1'
version = '1.2'
description = '`zipinfo` command parser'
author = 'Matt J'
author_email = 'https://github.com/listuser'
@@ -170,7 +170,8 @@ def parse(data, raw=False, quiet=False):
# 1st line
# Archive: log4j-core-2.16.0.jar
line = archive_item.pop(0)
_, archive = line.split()
# remove prefix but don't split on spaces for files/paths with spaces
archive = line.replace('Archive: ', '', 1)
# 2nd line
# Zip file size: 1789565 bytes, number of entries: 1218

202
jc/parsers/zpool_iostat.py Normal file
View File

@@ -0,0 +1,202 @@
"""jc - JSON Convert `zpool iostat` command output parser
Supports with or without the `-v` flag.
Usage (cli):
$ zpool iostat | jc --zpool-iostat
or
$ jc zpool iostat
Usage (module):
import jc
result = jc.parse('zpool_iostat', zpool_iostat_command_output)
Schema:
[
{
"pool": string,
"parent": string,
"cap_alloc": float,
"cap_alloc_unit": string,
"cap_free": float,
"cap_free_unit": string,
"ops_read": integer,
"ops_write": integer,
"bw_read": float,
"bw_read_unit": string,
"bw_write": float,
"bw_write_unit": string
}
]
Examples:
$ zpool iostat -v | jc --zpool-iostat -p
[
{
"pool": "zhgstera6",
"cap_alloc": 2.89,
"cap_free": 2.2,
"ops_read": 0,
"ops_write": 2,
"bw_read": 349.0,
"bw_write": 448.0,
"cap_alloc_unit": "T",
"cap_free_unit": "T",
"bw_read_unit": "K",
"bw_write_unit": "K"
},
{
"pool": "726060ALE614-K8JAPRGN:10",
"parent": "zhgstera6",
"cap_alloc": 2.89,
"cap_free": 2.2,
"ops_read": 0,
"ops_write": 2,
"bw_read": 349.0,
"bw_write": 448.0,
"cap_alloc_unit": "T",
"cap_free_unit": "T",
"bw_read_unit": "K",
"bw_write_unit": "K"
},
...
]
$ zpool iostat -v | jc --zpool-iostat -p -r
[
{
"pool": "zhgstera6",
"cap_alloc": "2.89T",
"cap_free": "2.20T",
"ops_read": "0",
"ops_write": "2",
"bw_read": "349K",
"bw_write": "448K"
},
{
"pool": "726060ALE614-K8JAPRGN:10",
"parent": "zhgstera6",
"cap_alloc": "2.89T",
"cap_free": "2.20T",
"ops_read": "0",
"ops_write": "2",
"bw_read": "349K",
"bw_write": "448K"
},
...
]
"""
from typing import List, Dict
from jc.jc_types import JSONDictType
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`zpool iostat` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'freebsd']
tags = ['command']
magic_commands = ['zpool iostat']
__version__ = info.version
def _process(proc_data: List[JSONDictType]) -> List[JSONDictType]:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
List of Dictionaries. Structured to conform to the schema.
"""
unit_values = {'cap_alloc', 'cap_free', 'bw_read', 'bw_write'}
int_list = {'ops_read', 'ops_write'}
for obj in proc_data:
for k, v in obj.copy().items():
if k in unit_values:
obj[k + '_unit'] = v[-1]
obj[k] = jc.utils.convert_to_float(v[:-1])
if k in int_list:
obj[k] = jc.utils.convert_to_int(v)
return proc_data
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> List[JSONDictType]:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List[Dict] = []
output_line: Dict = {}
pool_parent = ''
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
# skip non-data lines
if '---' in line or \
line.strip().endswith('bandwidth') or \
line.strip().endswith('write'):
continue
# data lines
line_list = line.strip().split()
if line.startswith(' '):
output_line = {
"pool": line_list[0],
"parent": pool_parent
}
else:
pool_parent = line_list[0]
output_line = {
"pool": pool_parent
}
output_line.update(
{
'cap_alloc': line_list[1],
'cap_free': line_list[2],
'ops_read': line_list[3],
'ops_write': line_list[4],
'bw_read': line_list[5],
'bw_write': line_list[6]
}
)
raw_output.append(output_line)
return raw_output if raw else _process(raw_output)

254
jc/parsers/zpool_status.py Normal file
View File

@@ -0,0 +1,254 @@
"""jc - JSON Convert `zpool status` command output parser
Works with or without the `-v` option.
Usage (cli):
$ zpool status | jc --zpool-status
or
$ jc zpool status
Usage (module):
import jc
result = jc.parse('zpool_status', zpool_status_command_output)
Schema:
[
{
"pool": string,
"state": string,
"status": string,
"action": string,
"see": string,
"scan": string,
"scrub": string,
"config": [
{
"name": string,
"state": string,
"read": integer,
"write": integer,
"checksum": integer,
"errors": string,
}
],
"errors": string
}
]
Examples:
$ zpool status -v | jc --zpool-status -p
[
{
"pool": "tank",
"state": "DEGRADED",
"status": "One or more devices could not be opened. Suffic...",
"action": "Attach the missing device and online it using 'zpool...",
"see": "http://www.sun.com/msg/ZFS-8000-2Q",
"scrub": "none requested",
"config": [
{
"name": "tank",
"state": "DEGRADED",
"read": 0,
"write": 0,
"checksum": 0
},
{
"name": "mirror-0",
"state": "DEGRADED",
"read": 0,
"write": 0,
"checksum": 0
},
{
"name": "c1t0d0",
"state": "ONLINE",
"read": 0,
"write": 0,
"checksum": 0
},
{
"name": "c1t1d0",
"state": "UNAVAIL",
"read": 0,
"write": 0,
"checksum": 0,
"errors": "cannot open"
}
],
"errors": "No known data errors"
}
]
$ zpool status -v | jc --zpool-status -p -r
[
{
"pool": "tank",
"state": "DEGRADED",
"status": "One or more devices could not be opened. Sufficient...",
"action": "Attach the missing device and online it using 'zpool...",
"see": "http://www.sun.com/msg/ZFS-8000-2Q",
"scrub": "none requested",
"config": [
{
"name": "tank",
"state": "DEGRADED",
"read": "0",
"write": "0",
"checksum": "0"
},
{
"name": "mirror-0",
"state": "DEGRADED",
"read": "0",
"write": "0",
"checksum": "0"
},
{
"name": "c1t0d0",
"state": "ONLINE",
"read": "0",
"write": "0",
"checksum": "0"
},
{
"name": "c1t1d0",
"state": "UNAVAIL",
"read": "0",
"write": "0",
"checksum": "0",
"errors": "cannot open"
}
],
"errors": "No known data errors"
}
]
"""
from typing import List, Dict
from jc.jc_types import JSONDictType
import jc.utils
from jc.parsers.kv import parse as kv_parse
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`zpool status` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'freebsd']
tags = ['command']
magic_commands = ['zpool status']
__version__ = info.version
def _process(proc_data: List[JSONDictType]) -> List[JSONDictType]:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
List of Dictionaries. Structured to conform to the schema.
"""
int_list = {'read', 'write', 'checksum'}
for obj in proc_data:
if 'config' in obj:
for conf in obj['config']:
for k, v in conf.items():
if k in int_list:
conf[k] = jc.utils.convert_to_int(v)
return proc_data
def _build_config_list(string: str) -> List[Dict]:
config_list: List = []
for line in filter(None, string.splitlines()):
if line.strip().endswith('READ WRITE CKSUM'):
continue
line_list = line.strip().split(maxsplit=5)
config_obj: Dict = {}
config_obj['name'] = line_list[0]
config_obj['state'] = line_list[1]
config_obj['read'] = line_list[2]
config_obj['write'] = line_list[3]
config_obj['checksum'] = line_list[4]
if len(line_list) == 6:
config_obj['errors'] = line_list[5]
config_list.append(config_obj)
return config_list
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> List[JSONDictType]:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List[Dict] = []
pool_str: str = ''
pool_obj: Dict = {}
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
if line.lstrip().startswith('pool: '):
if pool_str:
pool_obj = kv_parse(pool_str)
if 'config' in pool_obj:
pool_obj['config'] = _build_config_list(pool_obj['config'])
raw_output.append(pool_obj)
pool_str = ''
pool_str += line + '\n'
continue
# preserve indentation in continuation lines
if line.startswith(' '):
pool_str += line + '\n'
continue
# indent path lines for errors field
if line.startswith('/'):
pool_str += ' ' + line + '\n'
continue
# remove initial spaces from field start lines so we don't confuse line continuation
pool_str += line.strip() + '\n'
if pool_str:
pool_obj = kv_parse(pool_str)
if 'config' in pool_obj:
pool_obj['config'] = _build_config_list(pool_obj['config'])
raw_output.append(pool_obj)
return raw_output if raw else _process(raw_output)

244
man/jc.1
View File

@@ -1,28 +1,35 @@
.TH jc 1 2022-12-30 1.22.4 "JSON Convert"
.TH jc 1 2023-02-27 1.23.0 "JSON Convert"
.SH NAME
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types, and strings
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types,
and strings
.SH SYNOPSIS
Standard syntax:
.RS
COMMAND | \fBjc\fP [OPTIONS] PARSER
COMMAND | \fBjc\fP [SLICE] [OPTIONS] PARSER
cat FILE | \fBjc\fP [OPTIONS] PARSER
cat FILE | \fBjc\fP [SLICE] [OPTIONS] PARSER
echo STRING | \fBjc\fP [OPTIONS] PARSER
echo STRING | \fBjc\fP [SLICE] [OPTIONS] PARSER
.RE
Magic syntax:
.RS
\fBjc\fP [OPTIONS] COMMAND
\fBjc\fP [SLICE] [OPTIONS] COMMAND
\fBjc\fP [OPTIONS] /proc/<path-to-procfile>
\fBjc\fP [SLICE] [OPTIONS] /proc/<path-to-procfile>
.RE
.SH DESCRIPTION
\fBjc\fP JSONifies the output of many CLI tools, file-types, and common strings for easier parsing in scripts. \fBjc\fP accepts piped input from \fBSTDIN\fP and outputs a JSON representation of the previous command's output to \fBSTDOUT\fP. Alternatively, the "Magic" syntax can be used by prepending \fBjc\fP to the command to be converted. Options can be passed to \fBjc\fP immediately before the command is given. (Note: "Magic" syntax does not support shell builtins or command aliases)
\fBjc\fP JSONifies the output of many CLI tools, file-types, and common strings
for easier parsing in scripts. \fBjc\fP accepts piped input from \fBSTDIN\fP and
outputs a JSON representation of the previous command's output to \fBSTDOUT\fP.
Alternatively, the "Magic" syntax can be used by prepending \fBjc\fP to the
command to be converted. Options can be passed to \fBjc\fP immediately before
the command is given. (Note: "Magic" syntax does not support shell builtins or
command aliases)
.SH OPTIONS
.B
@@ -265,6 +272,11 @@ hashsum command parser (`md5sum`, `shasum`, etc.)
\fB--ini\fP
INI file parser
.TP
.B
\fB--ini-dup\fP
INI with duplicate key file parser
.TP
.B
\fB--iostat\fP
@@ -760,10 +772,15 @@ Semantic Version string parser
\fB--ss\fP
`ss` command parser
.TP
.B
\fB--ssh-conf\fP
`ssh` config file and `ssh -G` command parser
.TP
.B
\fB--sshd-conf\fP
sshd config file and `sshd -T` command parser
`sshd` config file and `sshd -T` command parser
.TP
.B
@@ -840,6 +857,11 @@ Syslog RFC 3164 string streaming parser
\fB--timestamp\fP
Unix Epoch Timestamp string parser
.TP
.B
\fB--toml\fP
TOML file parser
.TP
.B
\fB--top\fP
@@ -905,6 +927,11 @@ Unix Epoch Timestamp string parser
\fB--url\fP
URL string parser
.TP
.B
\fB--ver\fP
Version string parser
.TP
.B
\fB--vmstat\fP
@@ -955,6 +982,16 @@ YAML file parser
\fB--zipinfo\fP
`zipinfo` command parser
.TP
.B
\fB--zpool-iostat\fP
`zpool iostat` command parser
.TP
.B
\fB--zpool-status\fP
`zpool status` command parser
.RE
.PP
@@ -969,7 +1006,8 @@ About \fBjc\fP (JSON or YAML output)
.TP
.B
\fB-C\fP, \fB--force-color\fP
Force color output even when using pipes (overrides \fB-m\fP and the \fBNO_COLOR\fP env variable)
Force color output even when using pipes (overrides \fB-m\fP and the
\fBNO_COLOR\fP env variable)
.TP
.B
\fB-d\fP, \fB--debug\fP
@@ -977,7 +1015,8 @@ Debug - show traceback (use \fB-dd\fP for verbose traceback)
.TP
.B
\fB-h\fP, \fB--help\fP
Help (\fB--help --parser_name\fP for parser documentation). Use twice to show hidden parsers (e.g. \fB-hh\fP)
Help (\fB--help --parser_name\fP for parser documentation). Use twice to show
hidden parsers (e.g. \fB-hh\fP)
.TP
.B
\fB-m\fP, \fB--monochrome\fP
@@ -985,7 +1024,8 @@ Monochrome output
.TP
.B
\fB-M\fP, \fB--meta-out\fP
Add metadata to output including timestamp, parser name, magic command, magic command exit code, etc.
Add metadata to output including timestamp, parser name, magic command, magic
command exit code, etc.
.TP
.B
\fB-p\fP, \fB--pretty\fP
@@ -993,11 +1033,13 @@ Pretty print output
.TP
.B
\fB-q\fP, \fB--quiet\fP
Quiet mode. Suppresses parser warning messages (use -qq to ignore streaming parser errors)
Quiet mode. Suppresses parser warning messages (use -qq to ignore streaming
parser errors)
.TP
.B
\fB-r\fP, \fB--raw\fP
Raw output. Provides more literal output, typically with string values and no additional semantic processing
Raw output. Provides more literal output, typically with string values and no
additional semantic processing
.TP
.B
\fB-u\fP, \fB--unbuffer\fP
@@ -1019,10 +1061,93 @@ Generate Bash shell completion script
\fB-Z\fP, \fB--zsh-comp\fP
Generate Zsh shell completion script
.SH EXIT CODES
Any fatal errors within \fBjc\fP will generate an exit code of \fB100\fP, otherwise the exit code will be \fB0\fP.
.RE
.PP
.B
Slice:
.RS
Line slicing is supported using the \fBSTART:STOP\fP syntax similar to Python
slicing. This allows you to skip lines at the beginning and/or end of the
\fBSTDIN\fP input you would like \fBjc\fP to convert.
When using the "magic" syntax (e.g. \fBjc ifconfig eth0\fP), \fBjc\fP will store the exit code of the program being parsed and add it to the \fBjc\fP exit code. This way it is easier to determine if an error was from the parsed program or \fBjc\fP.
\fBSTART\fP and \fBSTOP\fP can be positive or negative integers or blank and
allow you to specify how many lines to skip and how many lines to process.
Positive and blank slices are the most memory efficient. Any negative
integers in the slice will use more memory.
For example, to skip the first and last line of the following text, you
could express the slice in a couple ways:
.RS
.nf
$ cat table.txt
### We want to skip this header ###
col1 col2
foo 1
bar 2
### We want to skip this footer ###
$ cat table.txt | jc 1:-1 --asciitable
[{"col1":"foo","col2":"1"},{"col1":"bar","col2":"2"}]
$ cat table.txt | jc 1:4 --asciitable
[{"col1":"foo","col2":"1"},{"col1":"bar","col2":"2"}]
.fi
.RE
In this example \fB1:-1\fP and \fB1:4\fP line slices provide the same output.
When using positive integers the index location of \fBSTOP\fP is non-inclusive.
Positive slices count from the first line of the input toward the end
starting at \fB0\fP as the first line. Negative slices count from the last line
toward the beginning starting at \fB-1\fP as the last line. This is also the way
Python's slicing feature works.
Here is a breakdown of line slice options:
.TP
.B
\fBSTART:STOP\fP
lines \fBSTART\fP through \fBSTOP - 1\fP
.TP
.B
\fBSTART:\fP
lines \fBSTART\fP through the rest of the output
.TP
.B
\fB:STOP\fP
lines from the beginning through \fBSTOP - 1\fP
.TP
.B
\fB-START:STOP\fP
\fBSTART\fP lines from the end through \fBSTOP - 1\fP
.TP
.B
\fBSTART:-STOP\fP
lines \fBSTART\fP through \fBSTOP\fP lines from the end
.TP
.B
\fB-START:-STOP\fP
\fBSTART\fP lines from the end through \fBSTOP\fP lines from the end
.TP
.B
\fB-START:\fP
\fBSTART\fP lines from the end through the rest of the output
.TP
.B
\fB:-STOP\fP
lines from the beginning through \fBSTOP\fP lines from the end
.TP
.B
\fB:\fP
all lines
.SH EXIT CODES
Any fatal errors within \fBjc\fP will generate an exit code of \fB100\fP,
otherwise the exit code will be \fB0\fP.
When using the "magic" syntax (e.g. \fBjc ifconfig eth0\fP), \fBjc\fP will store
the exit code of the program being parsed and add it to the \fBjc\fP exit code.
This way it is easier to determine if an error was from the parsed program or
\fBjc\fP.
Consider the following examples using \fBifconfig\fP:
@@ -1037,9 +1162,9 @@ ifconfig exit code = \fB1\fP, jc exit code = \fB100\fP, combined exit code = \fB
.RE
When using the "magic" syntax you can also retrieve the exit code of the called
program by using the \fB--meta-out\fP or \fB-M\fP option. This will append a \fB_jc_meta\fP
object to the output that will include the magic command information, including
the exit code.
program by using the \fB--meta-out\fP or \fB-M\fP option. This will append a
\fB_jc_meta\fP object to the output that will include the magic command
information, including the exit code.
Here is an example with \fBping\fP:
.RS
@@ -1081,11 +1206,16 @@ $ echo $?
\fBCustom Colors\fP
You can specify custom colors via the \fBJC_COLORS\fP environment variable. The \fBJC_COLORS\fP environment variable takes four comma separated string values in the following format:
You can specify custom colors via the \fBJC_COLORS\fP environment variable. The
\fBJC_COLORS\fP environment variable takes four comma separated string values in
the following format:
JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color>
Where colors are: \fBblack\fP, \fBred\fP, \fBgreen\fP, \fByellow\fP, \fBblue\fP, \fBmagenta\fP, \fBcyan\fP, \fBgray\fP, \fBbrightblack\fP, \fBbrightred\fP, \fBbrightgreen\fP, \fBbrightyellow\fP, \fBbrightblue\fP, \fBbrightmagenta\fP, \fBbrightcyan\fP, \fBwhite\fP, or \fBdefault\fP
Where colors are: \fBblack\fP, \fBred\fP, \fBgreen\fP, \fByellow\fP, \fBblue\fP,
\fBmagenta\fP, \fBcyan\fP, \fBgray\fP, \fBbrightblack\fP, \fBbrightred\fP,
\fBbrightgreen\fP, \fBbrightyellow\fP, \fBbrightblue\fP, \fBbrightmagenta\fP,
\fBbrightcyan\fP, \fBwhite\fP, or \fBdefault\fP
For example, to set to the default colors:
@@ -1099,10 +1229,20 @@ JC_COLORS=default,default,default,default
\fBDisable Color Output\fP
You can set the \fBNO_COLOR\fP environment variable to any value to disable color output in \fBjc\fP. Note that using the \fB-C\fP option to force color output will override both the \fBNO_COLOR\fP environment variable and the \fB-m\fP option.
You can set the \fBNO_COLOR\fP environment variable to any value to disable
color output in \fBjc\fP. Note that using the \fB-C\fP option to force color
output will override both the \fBNO_COLOR\fP environment variable and the
\fB-m\fP option.
.SH STREAMING PARSERS
Most parsers load all of the data from \fBSTDIN\fP, parse it, then output the entire JSON document serially. There are some streaming parsers (e.g. \fBls-s\fP, \fBping-s\fP, etc.) that immediately start processing and outputting the data line-by-line as JSON Lines (aka NDJSON) while it is being received from \fBSTDIN\fP. This can significantly reduce the amount of memory required to parse large amounts of command output (e.g. \fBls -lR /\fP) and can sometimes process the data more quickly. Streaming parsers have slightly different behavior than standard parsers as outlined below.
Most parsers load all of the data from \fBSTDIN\fP, parse it, then output the
entire JSON document serially. There are some streaming parsers (e.g.
\fBls-s\fP, \fBping-s\fP, etc.) that immediately start processing and outputting
the data line-by-line as JSON Lines (aka NDJSON) while it is being received from
\fBSTDIN\fP. This can significantly reduce the amount of memory required to
parse large amounts of command output (e.g. \fBls -lR /\fP) and can sometimes
process the data more quickly. Streaming parsers have slightly different
behavior than standard parsers as outlined below.
.RS
Note: Streaming parsers cannot be used with the "magic" syntax
@@ -1110,7 +1250,14 @@ Note: Streaming parsers cannot be used with the "magic" syntax
\fBIgnoring Errors\fP
You may want to ignore parsing errors when using streaming parsers since these may be used in long-lived processing pipelines and errors can break the pipe. To ignore parsing errors, use the \fB-qq\fP cli option. This will add a \fB_jc_meta\fP object to the JSON output with a \fBsuccess\fP attribute. If \fBsuccess\fP is \fBtrue\fP, then there were no issues parsing the line. If \fBsuccess\fP is \fBfalse\fP, then a parsing issue was found and \fBerror\fP and \fBline\fP fields will be added to include a short error description and the contents of the unparsable line, respectively:
You may want to ignore parsing errors when using streaming parsers since these
may be used in long-lived processing pipelines and errors can break the pipe. To
ignore parsing errors, use the \fB-qq\fP cli option. This will add a
\fB_jc_meta\fP object to the JSON output with a \fBsuccess\fP attribute. If
\fBsuccess\fP is \fBtrue\fP, then there were no issues parsing the line. If
\fBsuccess\fP is \fBfalse\fP, then a parsing issue was found and \fBerror\fP and
\fBline\fP fields will be added to include a short error description and the
contents of the unparsable line, respectively:
.RS
Successfully parsed line with \fB-qq\fP option:
@@ -1141,7 +1288,11 @@ Unsuccessfully parsed line with \fB-qq\fP option:
.RE
\fBUnbuffering Output\fP
Most operating systems will buffer output that is being piped from process to process. The buffer is usually around 4KB. When viewing the output in the terminal the OS buffer is not engaged so output is immediately displayed on the screen. When piping multiple processes together, though, it may seem as if the output is hanging when the input data is very slow (e.g. \fBping\fP):
Most operating systems will buffer output that is being piped from process to
process. The buffer is usually around 4KB. When viewing the output in the
terminal the OS buffer is not engaged so output is immediately displayed on the
screen. When piping multiple processes together, though, it may seem as if the
output is hanging when the input data is very slow (e.g. \fBping\fP):
.RS
.nf
@@ -1150,7 +1301,9 @@ $ ping 1.1.1.1 | jc \fB--ping-s\fP | jq
.fi
.RE
This is because the OS engages the 4KB buffer between \fBjc\fP and \fBjq\fP in this example. To display the data on the terminal in realtime, you can disable the buffer with the \fB-u\fP (unbuffer) cli option:
This is because the OS engages the 4KB buffer between \fBjc\fP and \fBjq\fP in
this example. To display the data on the terminal in realtime, you can disable
the buffer with the \fB-u\fP (unbuffer) cli option:
.RS
.nf
@@ -1164,7 +1317,8 @@ Note: Unbuffered output can be slower for large data streams.
.RE
.SH CUSTOM PARSERS
Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your local "App data directory":
Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your
local "App data directory":
.RS
.nf
@@ -1174,11 +1328,16 @@ Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your
.fi
.RE
Local parser plugins are standard python module files. Use the \fBjc/parsers/foo.py\fP or \fBjc/parsers/foo_s.py\fP (streaming) parser as a template and simply place a \fB.py\fP file in the \fBjcparsers\fP subfolder.
Local parser plugins are standard python module files. Use the
\fBjc/parsers/foo.py\fP or \fBjc/parsers/foo_s.py\fP (streaming) parser as a
template and simply place a \fB.py\fP file in the \fBjcparsers\fP subfolder.
Local plugin filenames must be valid python module names and therefore must start with a letter and consist entirely of alphanumerics and underscores. Local plugins may override default parsers.
Local plugin filenames must be valid python module names and therefore must
start with a letter and consist entirely of alphanumerics and underscores. Local
plugins may override default parsers.
Note: The application data directory follows the XDG Base Directory Specification
Note: The application data directory follows the \fBXDG Base Directory
Specification\fP
.SH CAVEATS
\fBLocale\fP
@@ -1203,9 +1362,13 @@ escape sequences if the \fBC\fP locale does not support UTF-8 encoding.
\fBTimezones\fP
Some parsers have calculated epoch timestamp fields added to the output. Unless a timestamp field name has a \fB_utc\fP suffix it is considered naive. (i.e. based on the local timezone of the system the \fBjc\fP parser was run on).
Some parsers have calculated epoch timestamp fields added to the output. Unless
a timestamp field name has a \fB_utc\fP suffix it is considered naive. (i.e.
based on the local timezone of the system the \fBjc\fP parser was run on).
If a UTC timezone can be detected in the text of the command output, the timestamp will be timezone aware and have a \fB_utc\fP suffix on the key name. (e.g. \fBepoch_utc\fP) No other timezones are supported for aware timestamps.
If a UTC timezone can be detected in the text of the command output, the
timestamp will be timezone aware and have a \fB_utc\fP suffix on the key name.
(e.g. \fBepoch_utc\fP) No other timezones are supported for aware timestamps.
.SH EXAMPLES
Standard Syntax:
@@ -1222,16 +1385,29 @@ $ jc \fB--pretty\fP dig www.google.com
$ jc \fB--pretty\fP /proc/meminfo
.RE
Line Slicing:
.RS
$ cat file.csv | jc \fB:101\fP \fB--csv\fP # parse first 100 lines
.RE
For parser documentation:
.RS
$ jc \fB--help\fP \fB--dig\fP
.RE
More Help:
.RS
$ jc \fB-hh\fP # show hidden parsers
$ jc \fB-hhh\fP # list parsers by category tags
.RE
.SH AUTHOR
Kelly Brazil (kellyjonbrazil@gmail.com)
https://github.com/kellyjonbrazil/jc
.SH COPYRIGHT
Copyright (c) 2019-2022 Kelly Brazil
Copyright (c) 2019-2023 Kelly Brazil
License: MIT License

View File

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

View File

@@ -1,28 +1,35 @@
.TH jc 1 {{ today }} {{ jc.version}} "JSON Convert"
.SH NAME
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types, and strings
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types,
and strings
.SH SYNOPSIS
Standard syntax:
.RS
COMMAND | \fBjc\fP [OPTIONS] PARSER
COMMAND | \fBjc\fP [SLICE] [OPTIONS] PARSER
cat FILE | \fBjc\fP [OPTIONS] PARSER
cat FILE | \fBjc\fP [SLICE] [OPTIONS] PARSER
echo STRING | \fBjc\fP [OPTIONS] PARSER
echo STRING | \fBjc\fP [SLICE] [OPTIONS] PARSER
.RE
Magic syntax:
.RS
\fBjc\fP [OPTIONS] COMMAND
\fBjc\fP [SLICE] [OPTIONS] COMMAND
\fBjc\fP [OPTIONS] /proc/<path-to-procfile>
\fBjc\fP [SLICE] [OPTIONS] /proc/<path-to-procfile>
.RE
.SH DESCRIPTION
\fBjc\fP JSONifies the output of many CLI tools, file-types, and common strings for easier parsing in scripts. \fBjc\fP accepts piped input from \fBSTDIN\fP and outputs a JSON representation of the previous command's output to \fBSTDOUT\fP. Alternatively, the "Magic" syntax can be used by prepending \fBjc\fP to the command to be converted. Options can be passed to \fBjc\fP immediately before the command is given. (Note: "Magic" syntax does not support shell builtins or command aliases)
\fBjc\fP JSONifies the output of many CLI tools, file-types, and common strings
for easier parsing in scripts. \fBjc\fP accepts piped input from \fBSTDIN\fP and
outputs a JSON representation of the previous command's output to \fBSTDOUT\fP.
Alternatively, the "Magic" syntax can be used by prepending \fBjc\fP to the
command to be converted. Options can be passed to \fBjc\fP immediately before
the command is given. (Note: "Magic" syntax does not support shell builtins or
command aliases)
.SH OPTIONS
.B
@@ -49,7 +56,8 @@ About \fBjc\fP (JSON or YAML output)
.TP
.B
\fB-C\fP, \fB--force-color\fP
Force color output even when using pipes (overrides \fB-m\fP and the \fBNO_COLOR\fP env variable)
Force color output even when using pipes (overrides \fB-m\fP and the
\fBNO_COLOR\fP env variable)
.TP
.B
\fB-d\fP, \fB--debug\fP
@@ -57,7 +65,8 @@ Debug - show traceback (use \fB-dd\fP for verbose traceback)
.TP
.B
\fB-h\fP, \fB--help\fP
Help (\fB--help --parser_name\fP for parser documentation). Use twice to show hidden parsers (e.g. \fB-hh\fP)
Help (\fB--help --parser_name\fP for parser documentation). Use twice to show
hidden parsers (e.g. \fB-hh\fP)
.TP
.B
\fB-m\fP, \fB--monochrome\fP
@@ -65,7 +74,8 @@ Monochrome output
.TP
.B
\fB-M\fP, \fB--meta-out\fP
Add metadata to output including timestamp, parser name, magic command, magic command exit code, etc.
Add metadata to output including timestamp, parser name, magic command, magic
command exit code, etc.
.TP
.B
\fB-p\fP, \fB--pretty\fP
@@ -73,11 +83,13 @@ Pretty print output
.TP
.B
\fB-q\fP, \fB--quiet\fP
Quiet mode. Suppresses parser warning messages (use -qq to ignore streaming parser errors)
Quiet mode. Suppresses parser warning messages (use -qq to ignore streaming
parser errors)
.TP
.B
\fB-r\fP, \fB--raw\fP
Raw output. Provides more literal output, typically with string values and no additional semantic processing
Raw output. Provides more literal output, typically with string values and no
additional semantic processing
.TP
.B
\fB-u\fP, \fB--unbuffer\fP
@@ -99,10 +111,93 @@ Generate Bash shell completion script
\fB-Z\fP, \fB--zsh-comp\fP
Generate Zsh shell completion script
.SH EXIT CODES
Any fatal errors within \fBjc\fP will generate an exit code of \fB100\fP, otherwise the exit code will be \fB0\fP.
.RE
.PP
.B
Slice:
.RS
Line slicing is supported using the \fBSTART:STOP\fP syntax similar to Python
slicing. This allows you to skip lines at the beginning and/or end of the
\fBSTDIN\fP input you would like \fBjc\fP to convert.
When using the "magic" syntax (e.g. \fBjc ifconfig eth0\fP), \fBjc\fP will store the exit code of the program being parsed and add it to the \fBjc\fP exit code. This way it is easier to determine if an error was from the parsed program or \fBjc\fP.
\fBSTART\fP and \fBSTOP\fP can be positive or negative integers or blank and
allow you to specify how many lines to skip and how many lines to process.
Positive and blank slices are the most memory efficient. Any negative
integers in the slice will use more memory.
For example, to skip the first and last line of the following text, you
could express the slice in a couple ways:
.RS
.nf
$ cat table.txt
### We want to skip this header ###
col1 col2
foo 1
bar 2
### We want to skip this footer ###
$ cat table.txt | jc 1:-1 --asciitable
[{"col1":"foo","col2":"1"},{"col1":"bar","col2":"2"}]
$ cat table.txt | jc 1:4 --asciitable
[{"col1":"foo","col2":"1"},{"col1":"bar","col2":"2"}]
.fi
.RE
In this example \fB1:-1\fP and \fB1:4\fP line slices provide the same output.
When using positive integers the index location of \fBSTOP\fP is non-inclusive.
Positive slices count from the first line of the input toward the end
starting at \fB0\fP as the first line. Negative slices count from the last line
toward the beginning starting at \fB-1\fP as the last line. This is also the way
Python's slicing feature works.
Here is a breakdown of line slice options:
.TP
.B
\fBSTART:STOP\fP
lines \fBSTART\fP through \fBSTOP - 1\fP
.TP
.B
\fBSTART:\fP
lines \fBSTART\fP through the rest of the output
.TP
.B
\fB:STOP\fP
lines from the beginning through \fBSTOP - 1\fP
.TP
.B
\fB-START:STOP\fP
\fBSTART\fP lines from the end through \fBSTOP - 1\fP
.TP
.B
\fBSTART:-STOP\fP
lines \fBSTART\fP through \fBSTOP\fP lines from the end
.TP
.B
\fB-START:-STOP\fP
\fBSTART\fP lines from the end through \fBSTOP\fP lines from the end
.TP
.B
\fB-START:\fP
\fBSTART\fP lines from the end through the rest of the output
.TP
.B
\fB:-STOP\fP
lines from the beginning through \fBSTOP\fP lines from the end
.TP
.B
\fB:\fP
all lines
.SH EXIT CODES
Any fatal errors within \fBjc\fP will generate an exit code of \fB100\fP,
otherwise the exit code will be \fB0\fP.
When using the "magic" syntax (e.g. \fBjc ifconfig eth0\fP), \fBjc\fP will store
the exit code of the program being parsed and add it to the \fBjc\fP exit code.
This way it is easier to determine if an error was from the parsed program or
\fBjc\fP.
Consider the following examples using \fBifconfig\fP:
@@ -117,9 +212,9 @@ ifconfig exit code = \fB1\fP, jc exit code = \fB100\fP, combined exit code = \fB
.RE
When using the "magic" syntax you can also retrieve the exit code of the called
program by using the \fB--meta-out\fP or \fB-M\fP option. This will append a \fB_jc_meta\fP
object to the output that will include the magic command information, including
the exit code.
program by using the \fB--meta-out\fP or \fB-M\fP option. This will append a
\fB_jc_meta\fP object to the output that will include the magic command
information, including the exit code.
Here is an example with \fBping\fP:
.RS
@@ -161,11 +256,16 @@ $ echo $?
\fBCustom Colors\fP
You can specify custom colors via the \fBJC_COLORS\fP environment variable. The \fBJC_COLORS\fP environment variable takes four comma separated string values in the following format:
You can specify custom colors via the \fBJC_COLORS\fP environment variable. The
\fBJC_COLORS\fP environment variable takes four comma separated string values in
the following format:
JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color>
Where colors are: \fBblack\fP, \fBred\fP, \fBgreen\fP, \fByellow\fP, \fBblue\fP, \fBmagenta\fP, \fBcyan\fP, \fBgray\fP, \fBbrightblack\fP, \fBbrightred\fP, \fBbrightgreen\fP, \fBbrightyellow\fP, \fBbrightblue\fP, \fBbrightmagenta\fP, \fBbrightcyan\fP, \fBwhite\fP, or \fBdefault\fP
Where colors are: \fBblack\fP, \fBred\fP, \fBgreen\fP, \fByellow\fP, \fBblue\fP,
\fBmagenta\fP, \fBcyan\fP, \fBgray\fP, \fBbrightblack\fP, \fBbrightred\fP,
\fBbrightgreen\fP, \fBbrightyellow\fP, \fBbrightblue\fP, \fBbrightmagenta\fP,
\fBbrightcyan\fP, \fBwhite\fP, or \fBdefault\fP
For example, to set to the default colors:
@@ -179,10 +279,20 @@ JC_COLORS=default,default,default,default
\fBDisable Color Output\fP
You can set the \fBNO_COLOR\fP environment variable to any value to disable color output in \fBjc\fP. Note that using the \fB-C\fP option to force color output will override both the \fBNO_COLOR\fP environment variable and the \fB-m\fP option.
You can set the \fBNO_COLOR\fP environment variable to any value to disable
color output in \fBjc\fP. Note that using the \fB-C\fP option to force color
output will override both the \fBNO_COLOR\fP environment variable and the
\fB-m\fP option.
.SH STREAMING PARSERS
Most parsers load all of the data from \fBSTDIN\fP, parse it, then output the entire JSON document serially. There are some streaming parsers (e.g. \fBls-s\fP, \fBping-s\fP, etc.) that immediately start processing and outputting the data line-by-line as JSON Lines (aka NDJSON) while it is being received from \fBSTDIN\fP. This can significantly reduce the amount of memory required to parse large amounts of command output (e.g. \fBls -lR /\fP) and can sometimes process the data more quickly. Streaming parsers have slightly different behavior than standard parsers as outlined below.
Most parsers load all of the data from \fBSTDIN\fP, parse it, then output the
entire JSON document serially. There are some streaming parsers (e.g.
\fBls-s\fP, \fBping-s\fP, etc.) that immediately start processing and outputting
the data line-by-line as JSON Lines (aka NDJSON) while it is being received from
\fBSTDIN\fP. This can significantly reduce the amount of memory required to
parse large amounts of command output (e.g. \fBls -lR /\fP) and can sometimes
process the data more quickly. Streaming parsers have slightly different
behavior than standard parsers as outlined below.
.RS
Note: Streaming parsers cannot be used with the "magic" syntax
@@ -190,7 +300,14 @@ Note: Streaming parsers cannot be used with the "magic" syntax
\fBIgnoring Errors\fP
You may want to ignore parsing errors when using streaming parsers since these may be used in long-lived processing pipelines and errors can break the pipe. To ignore parsing errors, use the \fB-qq\fP cli option. This will add a \fB_jc_meta\fP object to the JSON output with a \fBsuccess\fP attribute. If \fBsuccess\fP is \fBtrue\fP, then there were no issues parsing the line. If \fBsuccess\fP is \fBfalse\fP, then a parsing issue was found and \fBerror\fP and \fBline\fP fields will be added to include a short error description and the contents of the unparsable line, respectively:
You may want to ignore parsing errors when using streaming parsers since these
may be used in long-lived processing pipelines and errors can break the pipe. To
ignore parsing errors, use the \fB-qq\fP cli option. This will add a
\fB_jc_meta\fP object to the JSON output with a \fBsuccess\fP attribute. If
\fBsuccess\fP is \fBtrue\fP, then there were no issues parsing the line. If
\fBsuccess\fP is \fBfalse\fP, then a parsing issue was found and \fBerror\fP and
\fBline\fP fields will be added to include a short error description and the
contents of the unparsable line, respectively:
.RS
Successfully parsed line with \fB-qq\fP option:
@@ -221,7 +338,11 @@ Unsuccessfully parsed line with \fB-qq\fP option:
.RE
\fBUnbuffering Output\fP
Most operating systems will buffer output that is being piped from process to process. The buffer is usually around 4KB. When viewing the output in the terminal the OS buffer is not engaged so output is immediately displayed on the screen. When piping multiple processes together, though, it may seem as if the output is hanging when the input data is very slow (e.g. \fBping\fP):
Most operating systems will buffer output that is being piped from process to
process. The buffer is usually around 4KB. When viewing the output in the
terminal the OS buffer is not engaged so output is immediately displayed on the
screen. When piping multiple processes together, though, it may seem as if the
output is hanging when the input data is very slow (e.g. \fBping\fP):
.RS
.nf
@@ -230,7 +351,9 @@ $ ping 1.1.1.1 | jc \fB--ping-s\fP | jq
.fi
.RE
This is because the OS engages the 4KB buffer between \fBjc\fP and \fBjq\fP in this example. To display the data on the terminal in realtime, you can disable the buffer with the \fB-u\fP (unbuffer) cli option:
This is because the OS engages the 4KB buffer between \fBjc\fP and \fBjq\fP in
this example. To display the data on the terminal in realtime, you can disable
the buffer with the \fB-u\fP (unbuffer) cli option:
.RS
.nf
@@ -244,7 +367,8 @@ Note: Unbuffered output can be slower for large data streams.
.RE
.SH CUSTOM PARSERS
Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your local "App data directory":
Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your
local "App data directory":
.RS
.nf
@@ -254,11 +378,16 @@ Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your
.fi
.RE
Local parser plugins are standard python module files. Use the \fBjc/parsers/foo.py\fP or \fBjc/parsers/foo_s.py\fP (streaming) parser as a template and simply place a \fB.py\fP file in the \fBjcparsers\fP subfolder.
Local parser plugins are standard python module files. Use the
\fBjc/parsers/foo.py\fP or \fBjc/parsers/foo_s.py\fP (streaming) parser as a
template and simply place a \fB.py\fP file in the \fBjcparsers\fP subfolder.
Local plugin filenames must be valid python module names and therefore must start with a letter and consist entirely of alphanumerics and underscores. Local plugins may override default parsers.
Local plugin filenames must be valid python module names and therefore must
start with a letter and consist entirely of alphanumerics and underscores. Local
plugins may override default parsers.
Note: The application data directory follows the XDG Base Directory Specification
Note: The application data directory follows the \fBXDG Base Directory
Specification\fP
.SH CAVEATS
\fBLocale\fP
@@ -283,9 +412,13 @@ escape sequences if the \fBC\fP locale does not support UTF-8 encoding.
\fBTimezones\fP
Some parsers have calculated epoch timestamp fields added to the output. Unless a timestamp field name has a \fB_utc\fP suffix it is considered naive. (i.e. based on the local timezone of the system the \fBjc\fP parser was run on).
Some parsers have calculated epoch timestamp fields added to the output. Unless
a timestamp field name has a \fB_utc\fP suffix it is considered naive. (i.e.
based on the local timezone of the system the \fBjc\fP parser was run on).
If a UTC timezone can be detected in the text of the command output, the timestamp will be timezone aware and have a \fB_utc\fP suffix on the key name. (e.g. \fBepoch_utc\fP) No other timezones are supported for aware timestamps.
If a UTC timezone can be detected in the text of the command output, the
timestamp will be timezone aware and have a \fB_utc\fP suffix on the key name.
(e.g. \fBepoch_utc\fP) No other timezones are supported for aware timestamps.
.SH EXAMPLES
Standard Syntax:
@@ -302,10 +435,23 @@ $ jc \fB--pretty\fP dig www.google.com
$ jc \fB--pretty\fP /proc/meminfo
.RE
Line Slicing:
.RS
$ cat file.csv | jc \fB:101\fP \fB--csv\fP # parse first 100 lines
.RE
For parser documentation:
.RS
$ jc \fB--help\fP \fB--dig\fP
.RE
More Help:
.RS
$ jc \fB-hh\fP # show hidden parsers
$ jc \fB-hhh\fP # list parsers by category tags
.RE
.SH AUTHOR
{{ jc.author }} ({{ jc.author_email }})

View File

@@ -3,7 +3,7 @@
> Check out the `jc` Python [package documentation](https://github.com/kellyjonbrazil/jc/tree/master/docs) for developers
> Try the `jc` [web demo](https://jc-web.onrender.com/)
> Try the `jc` [web demo](https://jc-web.onrender.com/) and [REST API](https://github.com/kellyjonbrazil/jc-restapi)
> JC is [now available](https://galaxy.ansible.com/community/general) as an
Ansible filter plugin in the `community.general` collection. See this
@@ -44,8 +44,8 @@ $ jc dig example.com | jq -r '.[].answer[].data'
93.184.216.34
```
`jc` can also be used as a python library. In this case the output will be
a python dictionary, a list of dictionaries, or even a
`jc` can also be used as a python library. In this case the returned value
will be a python dictionary, a list of dictionaries, or even a
[lazy iterable of dictionaries](#using-streaming-parsers-as-python-modules)
instead of JSON:
```python
@@ -133,9 +133,9 @@ on Github.
`jc` accepts piped input from `STDIN` and outputs a JSON representation of the
previous command's output to `STDOUT`.
```bash
COMMAND | jc [OPTIONS] PARSER
cat FILE | jc [OPTIONS] PARSER
echo STRING | jc [OPTIONS] PARSER
COMMAND | jc [SLICE] [OPTIONS] PARSER
cat FILE | jc [SLICE] [OPTIONS] PARSER
echo STRING | jc [SLICE] [OPTIONS] PARSER
```
Alternatively, the "magic" syntax can be used by prepending `jc` to the command
@@ -143,8 +143,8 @@ to be converted or in front of the absolute path for Proc files. Options can be
passed to `jc` immediately before the command or Proc file path is given.
(Note: command aliases and shell builtins are not supported)
```bash
jc [OPTIONS] COMMAND
jc [OPTIONS] /proc/<path-to-procfile>
jc [SLICE] [OPTIONS] COMMAND
jc [SLICE] [OPTIONS] /proc/<path-to-procfile>
```
The JSON output can be compact (default) or pretty formatted with the `-p`
@@ -154,7 +154,7 @@ option.
| Argument | Command or Filetype | Documentation |
|-------------------|---------------------------------------------------------|----------------------------------------------------------------------------|{% for parser in parsers %}
| `{{ "{:>15}".format(parser.argument) }}` | {{ "{:<55}".format(parser.description) }} | {{ "{:<74}".format("[details](https://kellyjonbrazil.github.io/jc/docs/parsers/" + parser.name + ")") }} |{% endfor %}
| {{ "{:>17}".format("`" + parser.argument + "`") }} | {{ "{:<55}".format(parser.description) }} | {{ "{:<74}".format("[details](https://kellyjonbrazil.github.io/jc/docs/parsers/" + parser.name + ")") }} |{% endfor %}
### Options
@@ -175,6 +175,54 @@ option.
| `-B` | `--bash-comp` | Generate Bash shell completion script ([more info](https://github.com/kellyjonbrazil/jc/wiki/Shell-Completions)) |
| `-Z` | `--zsh-comp` | Generate Zsh shell completion script ([more info](https://github.com/kellyjonbrazil/jc/wiki/Shell-Completions)) |
### Slice
Line slicing is supported using the `START:STOP` syntax similar to Python
slicing. This allows you to skip lines at the beginning and/or end of the
`STDIN` input you would like `jc` to convert.
`START` and `STOP` can be positive or negative integers or blank and allow
you to specify how many lines to skip and how many lines to process.
Positive and blank slices are the most memory efficient. Any negative
integers in the slice will use more memory.
For example, to skip the first and last line of the following text, you
could express the slice in a couple ways:
```bash
$ cat table.txt
### We want to skip this header ###
col1 col2
foo 1
bar 2
### We want to skip this footer ###
$ cat table.txt | jc 1:-1 --asciitable
[{"col1":"foo","col2":"1"},{"col1":"bar","col2":"2"}]
$ cat table.txt | jc 1:4 --asciitable
[{"col1":"foo","col2":"1"},{"col1":"bar","col2":"2"}]
```
In this example `1:-1` and `1:4` line slices provide the same output.
When using positive integers the index location of `STOP` is non-inclusive.
Positive slices count from the first line of the input toward the end
starting at `0` as the first line. Negative slices count from the last line
toward the beginning starting at `-1` as the last line. This is also the way
[Python's slicing](https://stackoverflow.com/questions/509211/understanding-slicing)
feature works.
Here is a breakdown of line slice options:
| Slice Notation | Input Lines Processed |
|----------------|--------------------------------------------------------------|
| `START:STOP` | lines `START` through `STOP - 1` |
| `START:` | lines `START` through the rest of the output |
| `:STOP` | lines from the beginning through `STOP - 1` |
| `-START:STOP` | `START` lines from the end through `STOP - 1` |
| `START:-STOP` | lines `START` through `STOP` lines from the end |
| `-START:-STOP` | `START` lines from the end through `STOP` lines from the end |
| `-START:` | `START` lines from the end through the rest of the output |
| `:-STOP` | lines from the beginning through `STOP` lines from the end |
| `:` | all lines |
### Exit Codes
Any fatal errors within `jc` will generate an exit code of `100`, otherwise the
exit code will be `0`.
@@ -622,37 +670,31 @@ ifconfig | jc -p --ifconfig # or: jc -p ifconfig
cat example.ini
```
```
[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes
foo = fiz
bar = buz
[bitbucket.org]
User = hg
[section1]
fruit = apple
color = blue
[topsecret.server.com]
Port = 50022
ForwardX11 = no
[section2]
fruit = pear
color = green
```
```bash
cat example.ini | jc -p --ini
```
```json
{
"bitbucket.org": {
"ServeraLiveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "yes",
"User": "hg"
"foo": "fiz",
"bar": "buz",
"section1": {
"fruit": "apple",
"color": "blue"
},
"topsecret.server.com": {
"ServeraLiveInterval": "45",
"Compression": "yes",
"CompressionLevel": "9",
"ForwardX11": "no",
"Port": "50022"
"section2": {
"fruit": "pear",
"color": "green"
}
}
```

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

@@ -0,0 +1 @@
[{"name":null,"address":"1.2.3.4","hwtype":"ethernet","hwaddress":"5:6:7:8:9:a","permanent":true},{"name":"v630gw9-3-63-1","address":"9.3.63.1","hwtype":"ethernet","hwaddress":"0:0:5e:0:1:5c"},{"name":"v630vrrp9-3-63-2","address":"9.3.63.2","hwtype":"ethernet","hwaddress":"4c:96:14:59:d7:f0"},{"name":"v630vrrp9-3-63-3","address":"9.3.63.3","hwtype":"ethernet","hwaddress":"3c:8a:b0:0:8f:f0"},{"name":"caju6","address":"9.3.63.4","hwtype":null,"hwaddress":null},{"name":"rock-lp8","address":"9.3.63.173","hwtype":"ethernet","hwaddress":"b6:1b:da:7e:1f:2"}]

163
tests/fixtures/aix-7.1/arp-a.out vendored Normal file
View File

@@ -0,0 +1,163 @@
? (1.2.3.4) at 5:6:7:8:9:a [ethernet] permanent published stored in bucket 93
v630gw9-3-63-1 (9.3.63.1) at 0:0:5e:0:1:5c [ethernet] stored in bucket 97
v630vrrp9-3-63-2 (9.3.63.2) at 4c:96:14:59:d7:f0 [ethernet] stored in bucket 98
v630vrrp9-3-63-3 (9.3.63.3) at 3c:8a:b0:0:8f:f0 [ethernet] stored in bucket 99
caju6 (9.3.63.4) at (incomplete)
rock-lp8 (9.3.63.173) at b6:1b:da:7e:1f:2 [ethernet] stored in bucket 120
bucket: 0 contains: 0 entries
bucket: 1 contains: 0 entries
bucket: 2 contains: 0 entries
bucket: 3 contains: 0 entries
bucket: 4 contains: 0 entries
bucket: 5 contains: 0 entries
bucket: 6 contains: 0 entries
bucket: 7 contains: 0 entries
bucket: 8 contains: 0 entries
bucket: 9 contains: 0 entries
bucket: 10 contains: 0 entries
bucket: 11 contains: 0 entries
bucket: 12 contains: 0 entries
bucket: 13 contains: 0 entries
bucket: 14 contains: 0 entries
bucket: 15 contains: 0 entries
bucket: 16 contains: 0 entries
bucket: 17 contains: 0 entries
bucket: 18 contains: 0 entries
bucket: 19 contains: 0 entries
bucket: 20 contains: 0 entries
bucket: 21 contains: 0 entries
bucket: 22 contains: 0 entries
bucket: 23 contains: 0 entries
bucket: 24 contains: 0 entries
bucket: 25 contains: 0 entries
bucket: 26 contains: 0 entries
bucket: 27 contains: 0 entries
bucket: 28 contains: 0 entries
bucket: 29 contains: 0 entries
bucket: 30 contains: 0 entries
bucket: 31 contains: 0 entries
bucket: 32 contains: 0 entries
bucket: 33 contains: 0 entries
bucket: 34 contains: 0 entries
bucket: 35 contains: 0 entries
bucket: 36 contains: 0 entries
bucket: 37 contains: 0 entries
bucket: 38 contains: 0 entries
bucket: 39 contains: 0 entries
bucket: 40 contains: 0 entries
bucket: 41 contains: 0 entries
bucket: 42 contains: 0 entries
bucket: 43 contains: 0 entries
bucket: 44 contains: 0 entries
bucket: 45 contains: 0 entries
bucket: 46 contains: 0 entries
bucket: 47 contains: 0 entries
bucket: 48 contains: 0 entries
bucket: 49 contains: 0 entries
bucket: 50 contains: 0 entries
bucket: 51 contains: 0 entries
bucket: 52 contains: 0 entries
bucket: 53 contains: 0 entries
bucket: 54 contains: 0 entries
bucket: 55 contains: 0 entries
bucket: 56 contains: 0 entries
bucket: 57 contains: 0 entries
bucket: 58 contains: 0 entries
bucket: 59 contains: 0 entries
bucket: 60 contains: 0 entries
bucket: 61 contains: 0 entries
bucket: 62 contains: 0 entries
bucket: 63 contains: 0 entries
bucket: 64 contains: 0 entries
bucket: 65 contains: 0 entries
bucket: 66 contains: 0 entries
bucket: 67 contains: 0 entries
bucket: 68 contains: 0 entries
bucket: 69 contains: 0 entries
bucket: 70 contains: 0 entries
bucket: 71 contains: 0 entries
bucket: 72 contains: 0 entries
bucket: 73 contains: 0 entries
bucket: 74 contains: 0 entries
bucket: 75 contains: 0 entries
bucket: 76 contains: 0 entries
bucket: 77 contains: 0 entries
bucket: 78 contains: 0 entries
bucket: 79 contains: 0 entries
bucket: 80 contains: 0 entries
bucket: 81 contains: 0 entries
bucket: 82 contains: 0 entries
bucket: 83 contains: 0 entries
bucket: 84 contains: 0 entries
bucket: 85 contains: 0 entries
bucket: 86 contains: 0 entries
bucket: 87 contains: 0 entries
bucket: 88 contains: 0 entries
bucket: 89 contains: 0 entries
bucket: 90 contains: 0 entries
bucket: 91 contains: 0 entries
bucket: 92 contains: 0 entries
bucket: 93 contains: 1 entries
bucket: 94 contains: 0 entries
bucket: 95 contains: 0 entries
bucket: 96 contains: 0 entries
bucket: 97 contains: 1 entries
bucket: 98 contains: 1 entries
bucket: 99 contains: 1 entries
bucket: 100 contains: 0 entries
bucket: 101 contains: 0 entries
bucket: 102 contains: 0 entries
bucket: 103 contains: 0 entries
bucket: 104 contains: 0 entries
bucket: 105 contains: 0 entries
bucket: 106 contains: 0 entries
bucket: 107 contains: 0 entries
bucket: 108 contains: 0 entries
bucket: 109 contains: 0 entries
bucket: 110 contains: 0 entries
bucket: 111 contains: 0 entries
bucket: 112 contains: 0 entries
bucket: 113 contains: 0 entries
bucket: 114 contains: 0 entries
bucket: 115 contains: 0 entries
bucket: 116 contains: 0 entries
bucket: 117 contains: 0 entries
bucket: 118 contains: 0 entries
bucket: 119 contains: 0 entries
bucket: 120 contains: 1 entries
bucket: 121 contains: 0 entries
bucket: 122 contains: 0 entries
bucket: 123 contains: 0 entries
bucket: 124 contains: 0 entries
bucket: 125 contains: 0 entries
bucket: 126 contains: 0 entries
bucket: 127 contains: 0 entries
bucket: 128 contains: 0 entries
bucket: 129 contains: 0 entries
bucket: 130 contains: 0 entries
bucket: 131 contains: 0 entries
bucket: 132 contains: 0 entries
bucket: 133 contains: 0 entries
bucket: 134 contains: 0 entries
bucket: 135 contains: 0 entries
bucket: 136 contains: 0 entries
bucket: 137 contains: 0 entries
bucket: 138 contains: 0 entries
bucket: 139 contains: 0 entries
bucket: 140 contains: 0 entries
bucket: 141 contains: 0 entries
bucket: 142 contains: 0 entries
bucket: 143 contains: 0 entries
bucket: 144 contains: 0 entries
bucket: 145 contains: 0 entries
bucket: 146 contains: 0 entries
bucket: 147 contains: 0 entries
bucket: 148 contains: 0 entries
There are 5 entries in the arp table.

1
tests/fixtures/aix-7.1/mount.json vendored Normal file
View File

@@ -0,0 +1 @@
[{"filesystem":"/dev/hd4","mount_point":"/","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/hd2","mount_point":"/usr","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/hd9var","mount_point":"/var","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/hd3","mount_point":"/tmp","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/hd1","mount_point":"/home","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/hd11admin","mount_point":"/admin","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/proc","mount_point":"/proc","type":"procfs","options":["rw"]},{"filesystem":"/dev/hd10opt","mount_point":"/opt","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/livedump","mount_point":"/var/adm/ras/livedump","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/lvvarlog","mount_point":"/var/log","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/lvafslogs","mount_point":"/usr/afs/logs","type":"jfs2","options":["rw","log=/dev/hd8"]},{"filesystem":"/dev/fslv00","mount_point":"/sandbox","type":"jfs2","options":["rw","log=/dev/sboxlv_log"]},{"filesystem":"/dev/ramdisk0","mount_point":"/usr/vice/cache","type":"jfs","options":["rw","nointegrity"]},{"filesystem":"AFS","mount_point":"/afs","type":"afs","options":["rw"]},{"filesystem":"/local","mount_point":"/remote","type":"nfs3","options":["hard","intr","vers=3","sec=sys","proto=tcp","grpid","rsize=65536","wsize=65536","biods=16","nosuid"]}]

17
tests/fixtures/aix-7.1/mount.out vendored Normal file
View File

@@ -0,0 +1,17 @@
node mounted mounted over vfs date options
-------- --------------- --------------- ------ ------------ ---------------
/dev/hd4 / jfs2 Sep 06 11:46 rw,log=/dev/hd8
/dev/hd2 /usr jfs2 Sep 06 11:46 rw,log=/dev/hd8
/dev/hd9var /var jfs2 Sep 06 11:46 rw,log=/dev/hd8
/dev/hd3 /tmp jfs2 Sep 06 11:46 rw,log=/dev/hd8
/dev/hd1 /home jfs2 Sep 06 11:47 rw,log=/dev/hd8
/dev/hd11admin /admin jfs2 Sep 06 11:47 rw,log=/dev/hd8
/proc /proc procfs Sep 06 11:47 rw
/dev/hd10opt /opt jfs2 Sep 06 11:47 rw,log=/dev/hd8
/dev/livedump /var/adm/ras/livedump jfs2 Sep 06 11:47 rw,log=/dev/hd8
/dev/lvvarlog /var/log jfs2 Sep 06 11:47 rw,log=/dev/hd8
/dev/lvafslogs /usr/afs/logs jfs2 Sep 06 11:47 rw,log=/dev/hd8
/dev/fslv00 /sandbox jfs2 Sep 06 11:47 rw,log=/dev/sboxlv_log
/dev/ramdisk0 /usr/vice/cache jfs Sep 06 11:47 rw,nointegrity
AFS /afs afs Sep 06 11:47 rw
remote /local /remote nfs3 Sep 06 11:49 hard,intr,vers=3,sec=sys,proto=tcp,grpid,rsize=65536,wsize=65536,biods=16,nosuid

View File

@@ -0,0 +1 @@
[{"type":"Battery","id":0,"state":"Discharging","charge_percent":87,"design_capacity_mah":2110,"last_full_capacity":2271,"last_full_capacity_percent":100},{"type":"Battery","id":1,"state":"Discharging","charge_percent":98,"charge_remaining":"01:43:14","design_capacity_mah":4400,"last_full_capacity":3013,"last_full_capacity_percent":68,"charge_remaining_hours":1,"charge_remaining_minutes":43,"charge_remaining_seconds":14,"charge_remaining_total_seconds":6194},{"type":"Battery","id":2,"state":"Discharging","charge_percent":0},{"type":"Battery","id":3,"state":"Full","charge_percent":100},{"type":"Adapter","id":0,"on-line":true},{"type":"Adapter","id":1,"on-line":false},{"type":"Thermal","id":0,"mode":"ok","temperature":46.0,"temperature_unit":"C","trip_points":[{"id":0,"switches_to_mode":"critical","temperature":127.0,"temperature_unit":"C"},{"id":1,"switches_to_mode":"hot","temperature":127.0,"temperature_unit":"C"}]},{"type":"Thermal","id":1,"mode":"ok","temperature":55.0,"temperature_unit":"C","trip_points":[{"id":0,"switches_to_mode":"critical","temperature":130.0,"temperature_unit":"C"},{"id":1,"switches_to_mode":"hot","temperature":100.0,"temperature_unit":"C"}]},{"type":"Cooling","id":0,"messages":["Processor 0 of 10"]},{"type":"Cooling","id":1,"messages":["Processor 0 of 10"]},{"type":"Cooling","id":2,"messages":["x86_pkg_temp no state information available"]},{"type":"Cooling","id":3,"messages":["Processor 0 of 10"]},{"type":"Cooling","id":4,"messages":["intel_powerclamp no state information available","another message"]},{"type":"Cooling","id":5,"messages":["Processor 0 of 10"]}]

View File

@@ -0,0 +1,21 @@
Battery 0: Discharging, 87%, discharging at zero rate - will never fully discharge.
Battery 0: design capacity 2110 mAh, last full capacity 2271 mAh = 100%
Battery 1: Discharging, 98%, 01:43:14 remaining
Battery 1: design capacity 4400 mAh, last full capacity 3013 mAh = 68%
Battery 2: Discharging, 0%, rate information unavailable
Battery 3: Full, 100%
Adapter 0: on-line
Adapter 1: off-line
Thermal 0: ok, 46.0 degrees C
Thermal 0: trip point 0 switches to mode critical at temperature 127.0 degrees C
Thermal 0: trip point 1 switches to mode hot at temperature 127.0 degrees C
Thermal 1: ok, 55.0 degrees C
Thermal 1: trip point 0 switches to mode critical at temperature 130.0 degrees C
Thermal 1: trip point 1 switches to mode hot at temperature 100.0 degrees C
Cooling 0: Processor 0 of 10
Cooling 1: Processor 0 of 10
Cooling 2: x86_pkg_temp no state information available
Cooling 3: Processor 0 of 10
Cooling 4: intel_powerclamp no state information available
Cooling 4: another message
Cooling 5: Processor 0 of 10

View File

@@ -0,0 +1 @@
{"variables":[],"schedule":[{"occurrence":"daily","command":"/bin/sh do_the_thing"}]}

View File

@@ -0,0 +1,2 @@
#this is a test for the jc module
@daily /bin/sh do_the_thing

View File

@@ -0,0 +1 @@
{"variables":[],"schedule":[{"occurrence":"daily","user":"root","command":"/bin/sh do_the_thing"}]}

View File

@@ -0,0 +1,2 @@
#this is a test for the jc module
@daily root /bin/sh do_the_thing

View File

@@ -0,0 +1 @@
{"client":{"user":["foo"],"host":["localhost"],"password":["bar"]}}

View File

@@ -0,0 +1 @@
{"Settings":{"DetailedLog":["1"],"RunStatus":["1"],"StatusPort":["6090"],"StatusRefresh":["10"],"Archive":["1"],"LogFile":["/opt/ecs/mvuser/MV_IPTel/log/MV_IPTel.log"],"Version":["0.9 Build 4 Created July 11 2004 14:00"],"ServerName":["Unknown"]},"FTP":{"RunFTP":["1"],"FTPPort":["21"],"FTPDataPort":["20"],"FTPDir":["/opt/ecs/mvuser/MV_IPTel/data/FTPdata"],"FTP_TimeOut":["5"],"EnableSU":["1"],"SUUserName":["mvuser"],"SUPassword":["Avaya"]},"FTPS":{"RunFTPS":["0"],"FTPPort":["990"],"FTPDataPort":["889"]},"TFTP":{"RunTrivialFTP":["1"],"TrivialFTPPort":["69"],"TFTPDir":["/opt/ecs/mvuser/MV_IPTel/data/TFTPdata"]},"HTTP":{"RunHTTP":["1"],"HTTPPort":["81"],"HTTPDir":["/opt/ecs/mvuser/MV_IPTel/data/HTTPdata"]},"HTTPS":{"RunHTTPS":["0"],"HTTPSPort":["411"],"HTTPSDir":["/opt/ecs/mvuser/MV_IPTel/data/HTTPSdata"],"CertFile":["/opt/ecs/mvuser/MV_IPTel/certs/IPTelcert.pem"],"KeyFile":["/opt/ecs/mvuser/MV_IPTel/certs/IPTelkey.pem"],"ClientAuth":["0"],"IPTel":["0"],"SSLV2":["0"],"SSLV3":["0"],"TLSV1":["1"],"UseProxy":["0"],"ProxyAddr":["simon.avaya.com"],"ProxyPort":["9000"]},"BACKUP_SERVERS":{"FileServer":["0"],"RequestUpdates":["0"],"RequestBackup":["0"],"UsePrimarySvr":["0"],"PrimaryIP":["192.168.0.13"],"UseSecondarySvr":["0"],"SecondaryIP":["192.168.0.10"],"UpdateInterval":["2"],"CustomFTP":["1"],"CustomFTPDir":["home/mvuser/backup"],"CustomFTPUName":["tom"],"CustomFTPPwd":["jerry"],"CDRBackup":["0"],"BCMSBackup":["0"],"RetainDays":["7.0"]},"SNMP":{"UseSNMP":["1"]}}

View File

@@ -0,0 +1 @@
{"client":{"user":["foo"],"host":["localhost"],"password":["bar"]}}

View File

@@ -0,0 +1 @@
{"DEFAULT":{"ServerAliveInterval":["45"],"Compression":["yes"],"CompressionLevel":["9"],"ForwardX11":["yes"]},"bitbucket.org":{"User":["hg"]},"topsecret.server.com":{"Port":["50022"],"ForwardX11":["no"]}}

View File

@@ -1 +1 @@
{"bitbucket.org":{"ServerAliveInterval":"45","Compression":"yes","CompressionLevel":"9","ForwardX11":"yes","User":"hg"},"topsecret.server.com":{"ServerAliveInterval":"45","Compression":"yes","CompressionLevel":"9","ForwardX11":"no","Port":"50022"}}
{"DEFAULT":{"ServerAliveInterval":"45","Compression":"yes","CompressionLevel":"9","ForwardX11":"yes"},"bitbucket.org":{"User":"hg"},"topsecret.server.com":{"Port":"50022","ForwardX11":"no"}}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

45
tests/fixtures/generic/ssh_config1 vendored Normal file
View File

@@ -0,0 +1,45 @@
## override as per host ##
Host server1
HostName server1.cyberciti.biz
User nixcraft
Port 4242
IdentityFile /nfs/shared/users/nixcraft/keys/server1/id_rsa
## Home nas server ##
Host nas01
HostName 192.168.1.100
User root
IdentityFile ~/.ssh/nas01.key
## Login AWS Cloud ##
Host aws.apache
HostName 1.2.3.4
User wwwdata
IdentityFile ~/.ssh/aws.apache.key
## Login to internal lan server at 192.168.0.251 via our public uk office ssh based gateway using ##
## $ ssh uk.gw.lan ##
Host uk.gw.lan uk.lan
HostName 192.168.0.251
User nixcraft
ProxyCommand ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null
## Our Us Proxy Server ##
## Forward all local port 3128 traffic to port 3128 on the remote vps1.cyberciti.biz server ##
## $ ssh -f -N proxyus ##
Host proxyus
HostName vps1.cyberciti.biz
User breakfree
IdentityFile ~/.ssh/vps1.cyberciti.biz.key
LocalForward 3128 127.0.0.1:3128
### default for all ##
Host *
ForwardAgent no
ForwardX11 no
ForwardX11Trusted yes
User nixcraft
Port 22
Protocol 2
ServerAliveInterval 60
ServerAliveCountMax 30

View File

@@ -0,0 +1 @@
[{"host":"server1","host_list":["server1"],"hostname":"server1.cyberciti.biz","user":"nixcraft","port":4242,"identityfile":["/nfs/shared/users/nixcraft/keys/server1/id_rsa"]},{"host":"nas01","host_list":["nas01"],"hostname":"192.168.1.100","user":"root","identityfile":["~/.ssh/nas01.key"]},{"host":"aws.apache","host_list":["aws.apache"],"hostname":"1.2.3.4","user":"wwwdata","identityfile":["~/.ssh/aws.apache.key"]},{"host":"uk.gw.lan uk.lan","host_list":["uk.gw.lan","uk.lan"],"hostname":"192.168.0.251","user":"nixcraft","proxycommand":"ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null"},{"host":"proxyus","host_list":["proxyus"],"hostname":"vps1.cyberciti.biz","user":"breakfree","identityfile":["~/.ssh/vps1.cyberciti.biz.key"],"localforward":["3128 127.0.0.1:3128"]},{"host":"*","host_list":["*"],"forwardagent":"no","forwardx11":"no","forwardx11trusted":"yes","user":"nixcraft","port":22,"protocol":2,"serveraliveinterval":60,"serveralivecountmax":30}]

21
tests/fixtures/generic/ssh_config2 vendored Normal file
View File

@@ -0,0 +1,21 @@
Host targaryen
HostName 192.168.1.10
User daenerys
Port 7654
IdentityFile ~/.ssh/targaryen.key
Host tyrell
HostName 192.168.10.20
Host martell
HostName 192.168.10.50
Host *ell
user oberyn
Host * !martell
LogLevel INFO
Host *
User root
Compression yes

View File

@@ -0,0 +1 @@
[{"host":"targaryen","host_list":["targaryen"],"hostname":"192.168.1.10","user":"daenerys","port":7654,"identityfile":["~/.ssh/targaryen.key"]},{"host":"tyrell","host_list":["tyrell"],"hostname":"192.168.10.20"},{"host":"martell","host_list":["martell"],"hostname":"192.168.10.50"},{"host":"*ell","host_list":["*ell"],"user":"oberyn"},{"host":"* !martell","host_list":["*","!martell"],"loglevel":"INFO"},{"host":"*","host_list":["*"],"user":"root","compression":"yes"}]

33
tests/fixtures/generic/ssh_config3 vendored Normal file
View File

@@ -0,0 +1,33 @@
Host server1
HostName server1.cyberciti.biz
User nixcraft
Port 4242
IdentityFile /nfs/shared/users/nixcraft/keys/server1/id_rsa
## Home nas server ##
Host nas01
HostName 192.168.1.100
User root
IdentityFile ~/.ssh/nas01.key
## Login AWS Cloud ##
Host aws.apache
HostName 1.2.3.4
User wwwdata
IdentityFile ~/.ssh/aws.apache.key
## Login to internal lan server at 192.168.0.251 via our public uk office ssh based gateway using ##
## $ ssh uk.gw.lan ##
Host uk.gw.lan uk.lan
HostName 192.168.0.251
User nixcraft
ProxyCommand ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null
## Our Us Proxy Server ##
## Forward all local port 3128 traffic to port 3128 on the remote vps1.cyberciti.biz server ##
## $ ssh -f -N proxyus ##
Host proxyus
HostName vps1.cyberciti.biz
User breakfree
IdentityFile ~/.ssh/vps1.cyberciti.biz.key
LocalForward 3128 127.0.0.1:3128

View File

@@ -0,0 +1 @@
[{"host":"server1","host_list":["server1"],"hostname":"server1.cyberciti.biz","user":"nixcraft","port":4242,"identityfile":["/nfs/shared/users/nixcraft/keys/server1/id_rsa"]},{"host":"nas01","host_list":["nas01"],"hostname":"192.168.1.100","user":"root","identityfile":["~/.ssh/nas01.key"]},{"host":"aws.apache","host_list":["aws.apache"],"hostname":"1.2.3.4","user":"wwwdata","identityfile":["~/.ssh/aws.apache.key"]},{"host":"uk.gw.lan uk.lan","host_list":["uk.gw.lan","uk.lan"],"hostname":"192.168.0.251","user":"nixcraft","proxycommand":"ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null"},{"host":"proxyus","host_list":["proxyus"],"hostname":"vps1.cyberciti.biz","user":"breakfree","identityfile":["~/.ssh/vps1.cyberciti.biz.key"],"localforward":["3128 127.0.0.1:3128"]}]

105
tests/fixtures/generic/ssh_config4 vendored Normal file
View File

@@ -0,0 +1,105 @@
Host *
AddKeysToAgent ask
AddressFamily inet
BatchMode no
BindAddress 1.1.1.1
BindInterface en0
CanonicalDomains abc.com xyz.com
CanonicalizeFallbackLocal yes
CanonicalizeHostname none
CanonicalizeMaxDots 2
CanonicalizePermittedCNAMEs *.a.example.com:*.b.example.com,*.c.example.com
CASignatureAlgorithms ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ssh-ed25519@openssh.com
CertificateFile ~/certificates/cert1.pem
CertificateFile ~/certificates/cert2.pem
CheckHostIP yes
Ciphers 3des-cbc,aes128-cbc,aes192-cbc
ClearAllForwardings yes
Compression yes
ConnectionAttempts 9
ConnectTimeout 30
ControlMaster ask
ControlPath none
ControlPersist yes
DynamicForward 1.1.1.1:443
EnableEscapeCommandline no
EnableSSHKeysign yes
EscapeChar none
ExitOnForwardFailure yes
FingerprintHash md5
ForkAfterAuthentication yes
ForwardAgent $mypath
ForwardX11 no
ForwardX11Timeout 500
ForwardX11Trusted yes
GatewayPorts yes
GlobalKnownHostsFile /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2
GSSAPIAuthentication yes
GSSAPIDelegateCredentials yes
HashKnownHosts yes
HostbasedAcceptedAlgorithms ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com
HostbasedAuthentication yes
HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com
HostKeyAlias foobar
Hostname localhost
IdentitiesOnly yes
IdentityAgent SSH_AUTH_SOCK
IdentityFile ~/.ssh/vps1.cyberciti.biz.key
IdentityFile ~/.ssh/vps2.cyberciti.biz.key
IgnoreUnknown helloworld
Include ~/.ssh/config-extras ~/foo/bar
Include ~/.ssh/config-extra-extras
IPQoS af11 af12
KbdInteractiveAuthentication yes
KbdInteractiveDevices bsdauth,pam,skey
KexAlgorithms +sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org
KnownHostsCommand ~/checkknownhosts
LocalCommand ~/mycommand
LocalForward 3128 127.0.0.1:3128
LocalForward 3129 127.0.0.1:3129
LogLevel INFO
LogVerbose kex.c:*:1000,*:kex_exchange_identification():*,packet.c:*
MACs ^umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com
NoHostAuthenticationForLocalhost yes
NumberOfPasswordPrompts 3
PasswordAuthentication yes
PermitLocalCommand yes
PermitRemoteOpen 1.1.1.1:443 2.2.2.2:443
PKCS11Provider ~/pkcs11provider
Port 22
PreferredAuthentications gssapi-with-mic,hostbased,publickey,keyboard-interactive,password
Protocol 2
ProxyCommand ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null
ProxyJump 1.1.1.1:22,2.2.2.2:22
ProxyUseFdpass yes
PubkeyAcceptedAlgorithms -ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com
PubkeyAuthentication unbound
RekeyLimit 4G
RemoteCommand ~/mycommand
RemoteForward 1.1.1.1:22 2.2.2.2:22
RequestTTY force
RequiredRSASize 2048
RevokedHostKeys ~/revokedkeyfile
SecurityKeyProvider ~/keyprovider
SendEnv ENV1 ENV2
SendEnv ENV3
ServerAliveCountMax 3
ServerAliveInterval 3
SessionType none
SetEnv ENV1 ENV2
SetEnv ENV3
StdinNull yes
StreamLocalBindMask 0000
StreamLocalBindUnlink yes
StrictHostKeyChecking ask
SyslogFacility USER
TCPKeepAlive yes
Tunnel ethernet
TunnelDevice tun1:tun2
UpdateHostKeys ask
User nixcraft
UserKnownHostsFile ~/.ssh/knownhosts1 ~/.ssh/knownhosts2
VerifyHostKeyDNS ask
VisualHostKey yes
XAuthLocation /usr/X11R6/bin/xauth

View File

@@ -0,0 +1 @@
[{"host":"*","host_list":["*"],"addkeystoagent":"ask","addressfamily":"inet","batchmode":"no","bindaddress":"1.1.1.1","bindinterface":"en0","canonicaldomains":["abc.com","xyz.com"],"canonicalizefallbacklocal":"yes","canonicalizehostname":"none","canonicalizemaxdots":2,"canonicalizepermittedcnames":["*.a.example.com:*.b.example.com","*.c.example.com"],"casignaturealgorithms":["ssh-ed25519","ecdsa-sha2-nistp256","ecdsa-sha2-nistp384","ecdsa-sha2-nistp521","sk-ssh-ed25519@openssh.com"],"certificatefile":["~/certificates/cert1.pem","~/certificates/cert2.pem"],"checkhostip":"yes","ciphers":["3des-cbc","aes128-cbc","aes192-cbc"],"clearallforwardings":"yes","compression":"yes","connectionattempts":9,"connecttimeout":30,"controlmaster":"ask","controlpath":"none","controlpersist":"yes","dynamicforward":"1.1.1.1:443","enableescapecommandline":"no","enablesshkeysign":"yes","escapechar":"none","exitonforwardfailure":"yes","fingerprinthash":"md5","forkafterauthentication":"yes","forwardagent":"$mypath","forwardx11":"no","forwardx11timeout":500,"forwardx11trusted":"yes","gatewayports":"yes","globalknownhostsfile":["/etc/ssh/ssh_known_hosts","/etc/ssh/ssh_known_hosts2"],"gssapiauthentication":"yes","gssapidelegatecredentials":"yes","hashknownhosts":"yes","hostbasedacceptedalgorithms":["ssh-ed25519-cert-v01@openssh.com","ecdsa-sha2-nistp256-cert-v01@openssh.com"],"hostbasedauthentication":"yes","hostkeyalgorithms":["ssh-ed25519-cert-v01@openssh.com","ecdsa-sha2-nistp256-cert-v01@openssh.com"],"hostkeyalias":"foobar","hostname":"localhost","identitiesonly":"yes","identityagent":"SSH_AUTH_SOCK","identityfile":["~/.ssh/vps1.cyberciti.biz.key","~/.ssh/vps2.cyberciti.biz.key"],"ignoreunknown":"helloworld","include":["~/.ssh/config-extras","~/foo/bar","~/.ssh/config-extra-extras"],"ipqos":["af11","af12"],"kbdinteractiveauthentication":"yes","kbdinteractivedevices":["bsdauth","pam","skey"],"kexalgorithms":["sntrup761x25519-sha512@openssh.com","curve25519-sha256","curve25519-sha256@libssh.org"],"kexalgorithms_strategy":"+","knownhostscommand":"~/checkknownhosts","localcommand":"~/mycommand","localforward":["3128 127.0.0.1:3128","3129 127.0.0.1:3129"],"loglevel":"INFO","logverbose":["kex.c:*:1000","*:kex_exchange_identification():*","packet.c:*"],"macs":["umac-64-etm@openssh.com","umac-128-etm@openssh.com","hmac-sha2-256-etm@openssh.com","hmac-sha2-512-etm@openssh.com"],"macs_strategy":"^","nohostauthenticationforlocalhost":"yes","numberofpasswordprompts":3,"passwordauthentication":"yes","permitlocalcommand":"yes","permitremoteopen":["1.1.1.1:443","2.2.2.2:443"],"pkcs11provider":"~/pkcs11provider","port":22,"preferredauthentications":["gssapi-with-mic","hostbased","publickey","keyboard-interactive","password"],"protocol":2,"proxycommand":"ssh nixcraft@gateway.uk.cyberciti.biz nc %h %p 2> /dev/null","proxyjump":["1.1.1.1:22","2.2.2.2:22"],"proxyusefdpass":"yes","pubkeyacceptedalgorithms":["ssh-ed25519-cert-v01@openssh.com","ecdsa-sha2-nistp256-cert-v01@openssh.com"],"pubkeyacceptedalgorithms_strategy":"-","pubkeyauthentication":"unbound","rekeylimit":"4G","remotecommand":"~/mycommand","remoteforward":"1.1.1.1:22 2.2.2.2:22","requesttty":"force","requiredrsasize":2048,"revokedhostkeys":"~/revokedkeyfile","securitykeyprovider":"~/keyprovider","sendenv":["ENV1","ENV2","ENV3"],"serveralivecountmax":3,"serveraliveinterval":3,"sessiontype":"none","setenv":["ENV1","ENV2","ENV3"],"stdinnull":"yes","streamlocalbindmask":"0000","streamlocalbindunlink":"yes","stricthostkeychecking":"ask","syslogfacility":"USER","tcpkeepalive":"yes","tunnel":"ethernet","tunneldevice":"tun1:tun2","updatehostkeys":"ask","user":"nixcraft","userknownhostsfile":["~/.ssh/knownhosts1","~/.ssh/knownhosts2"],"verifyhostkeydns":"ask","visualhostkey":"yes","xauthlocation":"/usr/X11R6/bin/xauth"}]

14
tests/fixtures/generic/ssh_config5 vendored Normal file
View File

@@ -0,0 +1,14 @@
# comment
Host *
User something
# comment 2
Host svu
Hostname www.svuniversity.ac.in
# within-host-comment
Port 22
ProxyCommand nc -w 300 -x localhost:9050 %h %p
# another comment
# bla bla

View File

@@ -0,0 +1 @@
[{"host":"*","host_list":["*"],"user":"something"},{"host":"svu","host_list":["svu"],"hostname":"www.svuniversity.ac.in","port":22,"proxycommand":"nc -w 300 -x localhost:9050 %h %p"}]

View File

@@ -0,0 +1 @@
{"title":"TOML Example","owner":{"name":"Lance Uppercut","dob":296667120,"dob_iso":"1979-05-27T07:32:00-08:00"},"database":{"server":"192.168.1.1","ports":[8001,8001,8002],"connection_max":5000,"enabled":true},"servers":{"alpha":{"ip":"10.0.0.1","dc":"eqdc10"},"beta":{"ip":"10.0.0.2","dc":"eqdc10"}},"clients":{"data":[["gamma","delta"],[1,2]],"hosts":["alpha","omega"]}}

View File

@@ -0,0 +1,33 @@
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Lance Uppercut"
dob = 1979-05-27T07:32:00-08:00 # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ]
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]

View File

@@ -0,0 +1,27 @@
{
"fruit": [
{
"name": "apple",
"physical": {
"color": "red",
"shape": "round"
},
"variety": [
{
"name": "red delicious"
},
{
"name": "granny smith"
}
]
},
{
"name": "banana",
"variety": [
{
"name": "plantain"
}
]
}
]
}

View File

@@ -0,0 +1,18 @@
[[fruit]]
name = "apple"
[fruit.physical]
color = "red"
shape = "round"
[[fruit.variety]]
name = "red delicious"
[[fruit.variety]]
name = "granny smith"
[[fruit]]
name = "banana"
[[fruit.variety]]
name = "plantain"

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