mirror of
https://github.com/kellyjonbrazil/jc.git
synced 2025-12-24 00:31:11 +02:00
13
CHANGELOG
13
CHANGELOG
@@ -1,5 +1,18 @@
|
||||
jc changelog
|
||||
|
||||
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
|
||||
|
||||
16
EXAMPLES.md
16
EXAMPLES.md
@@ -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
|
||||
|
||||
42
README.md
42
README.md
@@ -201,6 +201,7 @@ option.
|
||||
| ` --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) |
|
||||
@@ -266,6 +267,7 @@ option.
|
||||
| ` --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) |
|
||||
@@ -756,37 +758,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 +1238,4 @@ cat istio.yaml | jc -p --yaml
|
||||
]
|
||||
```
|
||||
|
||||
© 2019-2022 Kelly Brazil
|
||||
© 2019-2023 Kelly Brazil
|
||||
@@ -4,7 +4,7 @@ _jc()
|
||||
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_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 --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 --vmstat --vmstat-s --w --wc --who --x509-cert --xml --xrandr --yaml --zipinfo)
|
||||
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)
|
||||
|
||||
@@ -102,7 +102,7 @@ _jc() {
|
||||
'xrandr:run "xrandr" command with magic syntax.'
|
||||
'zipinfo:run "zipinfo" 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 --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 --vmstat --vmstat-s --w --wc --who --x509-cert --xml --xrandr --yaml --zipinfo)
|
||||
jc_parsers_describe=(
|
||||
'--acpi:`acpi` command parser'
|
||||
'--airport:`airport -I` command parser'
|
||||
@@ -151,6 +151,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'
|
||||
@@ -265,6 +266,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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
121
docs/parsers/ini_dup.md
Normal 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)
|
||||
@@ -51,7 +51,6 @@ Schema:
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
$ iwconfig | jc --iwconfig -p
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
81
docs/parsers/toml.md
Normal file
81
docs/parsers/toml.md
Normal 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)
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -48,7 +48,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'
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from .jc_types import ParserInfoType, JSONDictType
|
||||
from jc import appdirs
|
||||
|
||||
|
||||
__version__ = '1.22.4'
|
||||
__version__ = '1.22.5'
|
||||
|
||||
parsers: List[str] = [
|
||||
'acpi',
|
||||
@@ -59,6 +59,7 @@ parsers: List[str] = [
|
||||
'id',
|
||||
'ifconfig',
|
||||
'ini',
|
||||
'ini-dup',
|
||||
'iostat',
|
||||
'iostat-s',
|
||||
'ip-address',
|
||||
@@ -174,6 +175,7 @@ parsers: List[str] = [
|
||||
'time',
|
||||
'timedatectl',
|
||||
'timestamp',
|
||||
'toml',
|
||||
'top',
|
||||
'top-s',
|
||||
'tracepath',
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
225
jc/parsers/ini_dup.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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)
|
||||
|
||||
149
jc/parsers/toml.py
Normal file
149
jc/parsers/toml.py
Normal 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
21
jc/parsers/tomli/LICENSE
Normal 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.
|
||||
11
jc/parsers/tomli/__init__.py
Normal file
11
jc/parsers/tomli/__init__.py
Normal 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
691
jc/parsers/tomli/_parser.py
Normal 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
101
jc/parsers/tomli/_re.py
Normal 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)
|
||||
8
jc/parsers/tomli/_types.py
Normal file
8
jc/parsers/tomli/_types.py
Normal 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
|
||||
1
jc/parsers/tomli/py.typed
Normal file
1
jc/parsers/tomli/py.typed
Normal file
@@ -0,0 +1 @@
|
||||
# Marker file for PEP 561
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
14
man/jc.1
14
man/jc.1
@@ -1,4 +1,4 @@
|
||||
.TH jc 1 2022-12-30 1.22.4 "JSON Convert"
|
||||
.TH jc 1 2023-01-11 1.22.5 "JSON Convert"
|
||||
.SH NAME
|
||||
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types, and strings
|
||||
.SH SYNOPSIS
|
||||
@@ -265,6 +265,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
|
||||
@@ -840,6 +845,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
|
||||
@@ -1232,6 +1242,6 @@ 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
|
||||
2
setup.py
2
setup.py
@@ -5,7 +5,7 @@ with open('README.md', 'r') as f:
|
||||
|
||||
setuptools.setup(
|
||||
name='jc',
|
||||
version='1.22.4',
|
||||
version='1.22.5',
|
||||
author='Kelly Brazil',
|
||||
author_email='kellyjonbrazil@gmail.com',
|
||||
description='Converts the output of popular command-line tools and file-types to JSON.',
|
||||
|
||||
@@ -622,37 +622,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
1
tests/fixtures/aix-7.1/arp-a.json
vendored
Normal 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
163
tests/fixtures/aix-7.1/arp-a.out
vendored
Normal 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
1
tests/fixtures/aix-7.1/mount.json
vendored
Normal 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
17
tests/fixtures/aix-7.1/mount.out
vendored
Normal 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
|
||||
1
tests/fixtures/generic/ini-dup-double-quote.json
vendored
Normal file
1
tests/fixtures/generic/ini-dup-double-quote.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"client":{"user":["foo"],"host":["localhost"],"password":["bar"]}}
|
||||
1
tests/fixtures/generic/ini-dup-iptelserver.json
vendored
Normal file
1
tests/fixtures/generic/ini-dup-iptelserver.json
vendored
Normal 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"]}}
|
||||
1
tests/fixtures/generic/ini-dup-single-quote.json
vendored
Normal file
1
tests/fixtures/generic/ini-dup-single-quote.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"client":{"user":["foo"],"host":["localhost"],"password":["bar"]}}
|
||||
1
tests/fixtures/generic/ini-dup-test.json
vendored
Normal file
1
tests/fixtures/generic/ini-dup-test.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"DEFAULT":{"ServerAliveInterval":["45"],"Compression":["yes"],"CompressionLevel":["9"],"ForwardX11":["yes"]},"bitbucket.org":{"User":["hg"]},"topsecret.server.com":{"Port":["50022"],"ForwardX11":["no"]}}
|
||||
2
tests/fixtures/generic/ini-test.json
vendored
2
tests/fixtures/generic/ini-test.json
vendored
@@ -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"}}
|
||||
|
||||
1
tests/fixtures/generic/lsusb-extra-hub-port-status-info.json
vendored
Normal file
1
tests/fixtures/generic/lsusb-extra-hub-port-status-info.json
vendored
Normal file
File diff suppressed because one or more lines are too long
4140
tests/fixtures/generic/lsusb-extra-hub-port-status-info.out
vendored
Normal file
4140
tests/fixtures/generic/lsusb-extra-hub-port-status-info.out
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
tests/fixtures/generic/toml-example.json
vendored
Normal file
1
tests/fixtures/generic/toml-example.json
vendored
Normal 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"]}}
|
||||
33
tests/fixtures/generic/toml-example.toml
vendored
Normal file
33
tests/fixtures/generic/toml-example.toml
vendored
Normal 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"
|
||||
]
|
||||
27
tests/fixtures/generic/toml-example2.json
vendored
Normal file
27
tests/fixtures/generic/toml-example2.json
vendored
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
18
tests/fixtures/generic/toml-example2.toml
vendored
Normal file
18
tests/fixtures/generic/toml-example2.toml
vendored
Normal 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"
|
||||
1
tests/fixtures/rhel-8/zipinfo-space-in-name.json
vendored
Normal file
1
tests/fixtures/rhel-8/zipinfo-space-in-name.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1221
tests/fixtures/rhel-8/zipinfo-space-in-name.out
vendored
Normal file
1221
tests/fixtures/rhel-8/zipinfo-space-in-name.out
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -42,6 +42,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-8/arp-a.out'), 'r', encoding='utf-8') as f:
|
||||
centos8_arp_a = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/aix-7.1/arp-a.out'), 'r', encoding='utf-8') as f:
|
||||
aix_7_1_arp_a = f.read()
|
||||
|
||||
# output
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/arp.json'), 'r', encoding='utf-8') as f:
|
||||
centos_7_7_arp_json = json.loads(f.read())
|
||||
@@ -76,6 +79,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-8/arp-a.json'), 'r', encoding='utf-8') as f:
|
||||
centos8_arp_a_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/aix-7.1/arp-a.json'), 'r', encoding='utf-8') as f:
|
||||
aix_7_1_arp_a_json = json.loads(f.read())
|
||||
|
||||
def test_arp_nodata(self):
|
||||
"""
|
||||
Test 'arp' with no data
|
||||
@@ -148,6 +154,13 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.arp.parse(self.centos8_arp_a, quiet=True), self.centos8_arp_a_json)
|
||||
|
||||
def test_arp_a_aix_7_1(self):
|
||||
"""
|
||||
Test 'arp -a' on AIX 7.1 with incomplete hw addresses and permanent ARP
|
||||
entries
|
||||
"""
|
||||
self.assertEqual(jc.parsers.arp.parse(self.aix_7_1_arp_a, quiet=True), self.aix_7_1_arp_a_json)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -58,11 +58,38 @@ class MyTests(unittest.TestCase):
|
||||
Test input that contains duplicate keys. Only the last value should be used.
|
||||
"""
|
||||
data = '''
|
||||
[section]
|
||||
duplicate_key: value1
|
||||
another_key = foo
|
||||
duplicate_key = value2
|
||||
'''
|
||||
expected = {'duplicate_key': 'value2', 'another_key': 'foo'}
|
||||
expected = {'section': {'duplicate_key': 'value2', 'another_key': 'foo'}}
|
||||
self.assertEqual(jc.parsers.ini.parse(data, quiet=True), expected)
|
||||
|
||||
def test_ini_missing_top_section(self):
|
||||
"""
|
||||
Test INI file missing top-level section header.
|
||||
"""
|
||||
data = '''
|
||||
key: value1
|
||||
another_key = foo
|
||||
[section2]
|
||||
key3: bar
|
||||
key4 =
|
||||
[section 3]
|
||||
key5 = "quoted"
|
||||
'''
|
||||
expected = {
|
||||
'key': 'value1',
|
||||
'another_key': 'foo',
|
||||
'section2': {
|
||||
'key3': 'bar',
|
||||
'key4': ''
|
||||
},
|
||||
'section 3': {
|
||||
'key5': 'quoted'
|
||||
}
|
||||
}
|
||||
self.assertEqual(jc.parsers.ini.parse(data, quiet=True), expected)
|
||||
|
||||
def test_ini_doublequote(self):
|
||||
|
||||
100
tests/test_ini_dup.py
Normal file
100
tests/test_ini_dup.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
import jc.parsers.ini_dup
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class MyTests(unittest.TestCase):
|
||||
|
||||
# input
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-test.ini'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_test = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-iptelserver.ini'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_iptelserver = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-double-quote.ini'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_double_quote = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-single-quote.ini'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_single_quote = f.read()
|
||||
|
||||
# output
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-dup-test.json'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_dup_test_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-dup-iptelserver.json'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_dup_iptelserver_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-dup-double-quote.json'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_dup_double_quote_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-dup-single-quote.json'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_dup_single_quote_json = json.loads(f.read())
|
||||
|
||||
|
||||
def test_ini_dup_nodata(self):
|
||||
"""
|
||||
Test the test ini file with no data
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini_dup.parse('', quiet=True), {})
|
||||
|
||||
def test_ini_dup_test(self):
|
||||
"""
|
||||
Test the test ini file
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(self.generic_ini_test, quiet=True), self.generic_ini_dup_test_json)
|
||||
|
||||
def test_ini_dup_iptelserver(self):
|
||||
"""
|
||||
Test the iptelserver ini file
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(self.generic_ini_iptelserver, quiet=True), self.generic_ini_dup_iptelserver_json)
|
||||
|
||||
def test_ini_dup_duplicate_keys(self):
|
||||
"""
|
||||
Test input that contains duplicate keys.
|
||||
"""
|
||||
data = '''
|
||||
[section]
|
||||
duplicate_key: value1
|
||||
another_key = foo
|
||||
duplicate_key = value2
|
||||
'''
|
||||
expected = {"section":{"duplicate_key":["value1","value2"],"another_key":["foo"]}}
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(data, quiet=True), expected)
|
||||
|
||||
def test_ini_dup_missing_top_section(self):
|
||||
"""
|
||||
Test INI file missing top-level section header.
|
||||
"""
|
||||
data = '''
|
||||
key: value1
|
||||
another_key = foo
|
||||
[section2]
|
||||
key3: bar
|
||||
key4 =
|
||||
[section 3]
|
||||
key5 = "quoted"
|
||||
'''
|
||||
expected = {"key":["value1"],"another_key":["foo"],"section2":{"key3":["bar"],"key4":[""]},"section 3":{"key5":["quoted"]}}
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(data, quiet=True), expected)
|
||||
|
||||
def test_ini_dup_doublequote(self):
|
||||
"""
|
||||
Test ini file with double quotes around a value
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(self.generic_ini_double_quote, quiet=True), self.generic_ini_dup_double_quote_json)
|
||||
|
||||
def test_ini_dup_singlequote(self):
|
||||
"""
|
||||
Test ini file with single quotes around a value
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(self.generic_ini_single_quote, quiet=True), self.generic_ini_dup_single_quote_json)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -53,6 +53,28 @@ duplicate_key = value2
|
||||
expected = {'duplicate_key': 'value2', 'another_key': 'foo'}
|
||||
self.assertEqual(jc.parsers.kv.parse(data, quiet=True), expected)
|
||||
|
||||
def test_kv_doublequote(self):
|
||||
"""
|
||||
Test kv string with double quotes around a value
|
||||
"""
|
||||
data = '''
|
||||
key1: "value1"
|
||||
key2: value2
|
||||
'''
|
||||
expected = {'key1': 'value1', 'key2': 'value2'}
|
||||
self.assertEqual(jc.parsers.kv.parse(data, quiet=True), expected)
|
||||
|
||||
def test_kv_singlequote(self):
|
||||
"""
|
||||
Test kv string with double quotes around a value
|
||||
"""
|
||||
data = '''
|
||||
key1: 'value1'
|
||||
key2: value2
|
||||
'''
|
||||
expected = {'key1': 'value1', 'key2': 'value2'}
|
||||
self.assertEqual(jc.parsers.kv.parse(data, quiet=True), expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -34,6 +34,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/lsusb-binary-object-store.out'), 'r', encoding='utf-8') as f:
|
||||
generic_lsusb_binary_object_store = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/lsusb-extra-hub-port-status-info.out'), 'r', encoding='utf-8') as f:
|
||||
generic_lsusb_extra_hub_port_status_info = f.read()
|
||||
|
||||
# output
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/lsusb.json'), 'r', encoding='utf-8') as f:
|
||||
centos_7_7_lsusb_json = json.loads(f.read())
|
||||
@@ -56,6 +59,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/lsusb-binary-object-store.json'), 'r', encoding='utf-8') as f:
|
||||
generic_lsusb_binary_object_store_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/lsusb-extra-hub-port-status-info.json'), 'r', encoding='utf-8') as f:
|
||||
generic_lsusb_extra_hub_port_status_info_json = json.loads(f.read())
|
||||
|
||||
|
||||
def test_lsusb_nodata(self):
|
||||
"""
|
||||
@@ -111,6 +117,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.lsusb.parse(self.generic_lsusb_binary_object_store, quiet=True), self.generic_lsusb_binary_object_store_json)
|
||||
|
||||
def test_lsusb_extra_hub_port_status_info(self):
|
||||
"""
|
||||
Test 'lsusb -v' with extra information in the hub port status section
|
||||
"""
|
||||
self.assertEqual(jc.parsers.lsusb.parse(self.generic_lsusb_extra_hub_port_status_info, quiet=True), self.generic_lsusb_extra_hub_port_status_info_json)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
@@ -21,6 +21,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/mount2.out'), 'r', encoding='utf-8') as f:
|
||||
osx_10_14_6_mount2 = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/aix-7.1/mount.out'), 'r', encoding='utf-8') as f:
|
||||
aix_7_1_mount = f.read()
|
||||
|
||||
# output
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/mount.json'), 'r', encoding='utf-8') as f:
|
||||
centos_7_7_mount_json = json.loads(f.read())
|
||||
@@ -34,6 +37,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/mount2.json'), 'r', encoding='utf-8') as f:
|
||||
osx_10_14_6_mount2_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/aix-7.1/mount.json'), 'r', encoding='utf-8') as f:
|
||||
aix_7_1_mount_json = json.loads(f.read())
|
||||
|
||||
|
||||
def test_mount_nodata(self):
|
||||
"""
|
||||
@@ -65,6 +71,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.mount.parse(self.osx_10_14_6_mount2, quiet=True), self.osx_10_14_6_mount2_json)
|
||||
|
||||
def test_mount_aix_7_1(self):
|
||||
"""
|
||||
Test 'mount' on OSX 10.14.6
|
||||
"""
|
||||
self.assertEqual(jc.parsers.mount.parse(self.aix_7_1_mount, quiet=True), self.aix_7_1_mount_json)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
59
tests/test_toml.py
Normal file
59
tests/test_toml.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import os
|
||||
import unittest
|
||||
import json
|
||||
from typing import Dict
|
||||
from jc.parsers.toml import parse
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class MyTests(unittest.TestCase):
|
||||
f_in: Dict = {}
|
||||
f_json: Dict = {}
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
fixtures = {
|
||||
'toml1': (
|
||||
'fixtures/generic/toml-example.toml',
|
||||
'fixtures/generic/toml-example.json'),
|
||||
'toml2': (
|
||||
'fixtures/generic/toml-example2.toml',
|
||||
'fixtures/generic/toml-example2.json')
|
||||
}
|
||||
|
||||
for file, filepaths in fixtures.items():
|
||||
with open(os.path.join(THIS_DIR, filepaths[0]), 'r', encoding='utf-8') as a, \
|
||||
open(os.path.join(THIS_DIR, filepaths[1]), 'r', encoding='utf-8') as b:
|
||||
cls.f_in[file] = a.read()
|
||||
cls.f_json[file] = json.loads(b.read())
|
||||
|
||||
|
||||
def test_toml_nodata(self):
|
||||
"""
|
||||
Test 'toml' with no data
|
||||
"""
|
||||
self.assertEqual(parse('', quiet=True), {})
|
||||
|
||||
|
||||
def test_toml_example1(self):
|
||||
"""
|
||||
Test 'toml' with first example file
|
||||
"""
|
||||
self.assertEqual(
|
||||
parse(self.f_in['toml1'], quiet=True),
|
||||
self.f_json['toml1']
|
||||
)
|
||||
|
||||
def test_toml_example2(self):
|
||||
"""
|
||||
Test 'toml' with second example file
|
||||
"""
|
||||
self.assertEqual(
|
||||
parse(self.f_in['toml2'], quiet=True),
|
||||
self.f_json['toml2']
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -15,6 +15,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/zipinfo-multi.out'), 'r', encoding='utf-8') as f:
|
||||
osx_10_14_6_zipinfo_multi = f.read()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/rhel-8/zipinfo-space-in-name.out'), 'r', encoding='utf-8') as f:
|
||||
rhel_8_zipinfo_space_in_name = f.read()
|
||||
|
||||
# output
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/rhel-8/zipinfo.json'), 'r', encoding='utf-8') as f:
|
||||
rhel_8_zipinfo_json = json.loads(f.read())
|
||||
@@ -22,6 +25,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/osx-10.14.6/zipinfo-multi.json'), 'r', encoding='utf-8') as f:
|
||||
osx_10_14_6_zipinfo_multi_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/rhel-8/zipinfo-space-in-name.json'), 'r', encoding='utf-8') as f:
|
||||
rhel_8_zipinfo_space_in_name_json = json.loads(f.read())
|
||||
|
||||
|
||||
def test_zipinfo_nodata(self):
|
||||
"""
|
||||
@@ -41,6 +47,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.zipinfo.parse(self.osx_10_14_6_zipinfo_multi, quiet=True), self.osx_10_14_6_zipinfo_multi_json)
|
||||
|
||||
def test_zipinfo_rhel_8_space_in_name(self):
|
||||
"""
|
||||
Test 'zipinfo' on Red Hat 8 with spaces in the file path
|
||||
"""
|
||||
self.assertEqual(jc.parsers.zipinfo.parse(self.rhel_8_zipinfo_space_in_name, quiet=True), self.rhel_8_zipinfo_space_in_name_json)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user