1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2025-06-17 00:07:37 +02:00
Files
jc/jc/lib.py
2022-11-07 13:23:55 -08:00

556 lines
15 KiB
Python

"""jc - JSON Convert lib module"""
import sys
import os
import re
import importlib
from typing import List, Iterable, Union, Iterator
from types import ModuleType
from .jc_types import ParserInfoType, JSONDictType
from jc import appdirs
__version__ = '1.22.2'
parsers: List[str] = [
'acpi',
'airport',
'airport-s',
'arp',
'asciitable',
'asciitable-m',
'blkid',
'cef',
'cef-s',
'chage',
'cksum',
'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',
'iso-datetime',
'iw-scan',
'jar-manifest',
'jobs',
'jwt',
'kv',
'last',
'ls',
'ls-s',
'lsblk',
'lsmod',
'lsof',
'lspci',
'lsusb',
'm3u',
'mdadm',
'mount',
'mpstat',
'mpstat-s',
'netstat',
'nmcli',
'ntpq',
'os-prober',
'passwd',
'pci-ids',
'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'
]
def _cliname_to_modname(parser_cli_name: str) -> str:
"""Return real module name (dashes converted to underscores)"""
return parser_cli_name.replace('--', '').replace('-', '_')
def _modname_to_cliname(parser_mod_name: str) -> str:
"""Return module's cli name (underscores converted to dashes)"""
return parser_mod_name.replace('_', '-')
# Create the local_parsers list. This is a list of custom or
# override parsers from <user_data_dir>/jc/jcparsers/*.py.
# Once this list is created, extend the parsers list with it.
local_parsers: List[str] = []
data_dir = appdirs.user_data_dir('jc', 'jc') # type: ignore
local_parsers_dir = os.path.join(data_dir, 'jcparsers')
if os.path.isdir(local_parsers_dir):
sys.path.append(data_dir)
for name in os.listdir(local_parsers_dir):
if re.match(r'\w+\.py$', name) and os.path.isfile(os.path.join(local_parsers_dir, name)):
plugin_name = name[0:-3]
local_parsers.append(_modname_to_cliname(plugin_name))
if plugin_name not in parsers:
parsers.append(_modname_to_cliname(plugin_name))
try:
del name
except Exception:
pass
def _parser_argument(parser_mod_name: str) -> str:
"""Return short name of the parser with dashes and with -- prefix"""
parser = _modname_to_cliname(parser_mod_name)
return f'--{parser}'
def _get_parser(parser_mod_name: str) -> ModuleType:
"""Return the parser module object"""
# ensure parser_mod_name is a true module name and not a cli name
parser_mod_name = _cliname_to_modname(parser_mod_name)
parser_cli_name = _modname_to_cliname(parser_mod_name)
modpath: str = 'jcparsers.' if parser_cli_name in local_parsers else 'jc.parsers.'
return importlib.import_module(f'{modpath}{parser_mod_name}')
def _parser_is_streaming(parser: ModuleType) -> bool:
"""
Returns True if this is a streaming parser, else False
parser is a parser module object.
"""
if getattr(parser.info, 'streaming', None):
return True
return False
def _parser_is_hidden(parser: ModuleType) -> bool:
"""
Returns True if this is a hidden parser, else False
parser is a parser module object.
"""
if getattr(parser.info, 'hidden', None):
return True
return False
def _parser_is_deprecated(parser: ModuleType) -> bool:
"""
Returns True if this is a deprecated parser, else False
parser is a parser module object.
"""
if getattr(parser.info, 'deprecated', None):
return True
return False
def parse(
parser_mod_name: Union[str, ModuleType],
data: Union[str, bytes, Iterable[str]],
quiet: bool = False,
raw: bool = False,
ignore_exceptions: bool = None,
**kwargs
) -> Union[JSONDictType, List[JSONDictType], Iterator[JSONDictType]]:
"""
Parse the data (string or bytes) using the supplied parser (string or
module object).
This function provides a high-level API to simplify parser use. This
function will call built-in parsers and custom plugin parsers.
Example (standard parsers):
>>> import jc
>>> date_obj = jc.parse('date', 'Tue Jan 18 10:23:07 PST 2022')
>>> print(f'The year is: {date_obj["year"]}')
The year is: 2022
Example (streaming parsers):
>>> import jc
>>> ping_gen = jc.parse('ping_s', ping_output.splitlines())
>>> for item in ping_gen:
>>> print(f'Response time: {item["time_ms"]} ms')
Response time: 102 ms
Response time: 109 ms
...
To get a list of available parser module names, use `parser_mod_list()`.
Alternatively, a parser module object can be supplied:
>>> import jc
>>> import jc.parsers.date as jc_date
>>> date_obj = jc.parse(jc_date, 'Tue Jan 18 10:23:07 PST 2022')
>>> print(f'The year is: {date_obj["year"]}')
The year is: 2022
You can also use the lower-level parser modules directly:
>>> import jc.parsers.date
>>> jc.parsers.date.parse('Tue Jan 18 10:23:07 PST 2022')
Though, accessing plugin parsers directly is a bit more cumbersome, so
this higher-level API is recommended. Here is how you can access plugin
parsers without this API:
>>> import os
>>> import sys
>>> import jc.appdirs
>>> data_dir = jc.appdirs.user_data_dir('jc', 'jc')
>>> local_parsers_dir = os.path.join(data_dir, 'jcparsers')
>>> sys.path.append(local_parsers_dir)
>>> import my_custom_parser
>>> my_custom_parser.parse('command_data')
Parameters:
parser_mod_name: (string or name of the parser module. This
Module) function will accept module_name,
cli-name, and --argument-name
variants of the module name.
A Module object can also be passed
directly or via _get_parser()
data: (string or data to parse (string or bytes for
bytes or standard parsers, iterable of
iterable) strings for streaming parsers)
raw: (boolean) output preprocessed JSON if True
quiet: (boolean) suppress warning messages if True
ignore_exceptions: (boolean) ignore parsing exceptions if True
(streaming parsers only)
Returns:
Standard Parsers: Dictionary or List of Dictionaries
Streaming Parsers: Generator Object containing Dictionaries
"""
if isinstance(parser_mod_name, ModuleType):
jc_parser = parser_mod_name
else:
jc_parser = _get_parser(parser_mod_name)
if ignore_exceptions is not None:
return jc_parser.parse(
data,
quiet=quiet,
raw=raw,
ignore_exceptions=ignore_exceptions,
**kwargs
)
return jc_parser.parse(data, quiet=quiet, raw=raw, **kwargs)
def parser_mod_list(
show_hidden: bool = False,
show_deprecated: bool = False
) -> List[str]:
"""Returns a list of all available parser module names."""
plist: List[str] = []
for p in parsers:
parser = _get_parser(p)
if not show_hidden and _parser_is_hidden(parser):
continue
if not show_deprecated and _parser_is_deprecated(parser):
continue
plist.append(_cliname_to_modname(p))
return plist
def plugin_parser_mod_list(
show_hidden: bool = False,
show_deprecated: bool = False
) -> List[str]:
"""
Returns a list of plugin parser module names. This function is a
subset of `parser_mod_list()`.
"""
plist: List[str] = []
for p in local_parsers:
parser = _get_parser(p)
if not show_hidden and _parser_is_hidden(parser):
continue
if not show_deprecated and _parser_is_deprecated(parser):
continue
plist.append(_cliname_to_modname(p))
return plist
def standard_parser_mod_list(
show_hidden: bool = False,
show_deprecated: bool = False
) -> List[str]:
"""
Returns a list of standard parser module names. This function is a
subset of `parser_mod_list()` and does not contain any streaming
parsers.
"""
plist: List[str] = []
for p in parsers:
parser = _get_parser(p)
if not _parser_is_streaming(parser):
if not show_hidden and _parser_is_hidden(parser):
continue
if not show_deprecated and _parser_is_deprecated(parser):
continue
plist.append(_cliname_to_modname(p))
return plist
def streaming_parser_mod_list(
show_hidden: bool = False,
show_deprecated: bool = False
) -> List[str]:
"""
Returns a list of streaming parser module names. This function is a
subset of `parser_mod_list()`.
"""
plist: List[str] = []
for p in parsers:
parser = _get_parser(p)
if _parser_is_streaming(parser):
if not show_hidden and _parser_is_hidden(parser):
continue
if not show_deprecated and _parser_is_deprecated(parser):
continue
plist.append(_cliname_to_modname(p))
return plist
def parser_info(
parser_mod_name: Union[str, ModuleType],
documentation: bool = False
) -> ParserInfoType:
"""
Returns a dictionary that includes the parser module metadata.
Parameters:
parser_mod_name: (string or name of the parser module. This
Module) function will accept module_name,
cli-name, and --argument-name
variants of the module name as well
as a parser module object.
documentation: (boolean) include parser docstring if True
"""
if isinstance(parser_mod_name, ModuleType):
parser_mod = parser_mod_name
parser_mod_name = parser_mod.__name__.split('.')[-1]
else:
# ensure parser_mod_name is a true module name and not a cli name
parser_mod_name = _cliname_to_modname(parser_mod_name)
parser_mod = _get_parser(parser_mod_name)
info_dict: ParserInfoType = {}
if hasattr(parser_mod, 'info'):
info_dict['name'] = parser_mod_name
info_dict['argument'] = _parser_argument(parser_mod_name)
parser_entry = vars(parser_mod.info)
for k, v in parser_entry.items():
if not k.startswith('__'):
info_dict[k] = v # type: ignore
if _modname_to_cliname(parser_mod_name) in local_parsers:
info_dict['plugin'] = True
if documentation:
docs = parser_mod.__doc__
if not docs:
docs = 'No documentation available.\n'
info_dict['documentation'] = docs
return info_dict
def all_parser_info(
documentation: bool = False,
show_hidden: bool = False,
show_deprecated: bool = False
) -> List[ParserInfoType]:
"""
Returns a list of dictionaries that includes metadata for all parser
modules. By default only non-hidden, non-deprecated parsers are
returned.
Parameters:
documentation: (boolean) include parser docstrings if True
show_hidden: (boolean) also show parsers marked as hidden
in their info metadata.
show_deprecated: (boolean) also show parsers marked as
deprecated in their info metadata.
"""
plist: List[str] = []
for p in parsers:
parser = _get_parser(p)
if not show_hidden and _parser_is_hidden(parser):
continue
if not show_deprecated and _parser_is_deprecated(parser):
continue
plist.append(_cliname_to_modname(p))
p_info_list: List[ParserInfoType] = [parser_info(p, documentation=documentation) for p in plist]
return p_info_list
def get_help(parser_mod_name: Union[str, ModuleType]) -> None:
"""
Show help screen for the selected parser.
This function will accept **module_name**, **cli-name**, and
**--argument-name** variants of the module name string as well as a
parser module object.
"""
if isinstance(parser_mod_name, ModuleType):
jc_parser = parser_mod_name
else:
jc_parser = _get_parser(parser_mod_name)
help(jc_parser)