mirror of
https://github.com/kellyjonbrazil/jc.git
synced 2025-06-19 00:17:51 +02:00
@ -1,5 +1,14 @@
|
||||
jc changelog
|
||||
|
||||
20240212 v1.25.1
|
||||
- Fix for crash when optional libraries are not installed (e.g. xmltodict)
|
||||
- Fix for `ini` parser crashing with some keys with no values
|
||||
- Fix `xrandr` parser to extract more EDID data
|
||||
- Enhance `uptime` parser to support output with no user information
|
||||
- Enhance `--quiet` CLI option to cover more warning messages
|
||||
- Add tests for missing optional libraries
|
||||
- Documentation updates
|
||||
|
||||
20240204 v1.25.0
|
||||
- Add `--slurp` functionality to wrap output from multiple lines into a single array.
|
||||
Note, this only works with single-line input parsers. (e.g. `date`, `ip-address`, `url`, etc.)
|
||||
|
@ -98,4 +98,4 @@ Compatibility: linux, darwin, cygwin, win32, aix, freebsd
|
||||
|
||||
Source: [`jc/parsers/ini.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/ini.py)
|
||||
|
||||
Version 2.1 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
Version 2.2 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
|
@ -76,4 +76,4 @@ Compatibility: linux, darwin, cygwin, win32, aix, freebsd
|
||||
|
||||
Source: [`jc/parsers/plist.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/plist.py)
|
||||
|
||||
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
|
@ -52,11 +52,11 @@ Specific Proc file parser names can be found with `jc -hh` or `jc -a`.
|
||||
|
||||
Schemas can also be found online at:
|
||||
|
||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_<name>
|
||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_<name>
|
||||
|
||||
For example:
|
||||
|
||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_meminfo
|
||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_meminfo
|
||||
|
||||
Examples:
|
||||
|
||||
|
@ -92,4 +92,4 @@ Source: [`jc/parsers/uptime.py`](https://github.com/kellyjonbrazil/jc/blob/maste
|
||||
|
||||
This parser can be used with the `--slurp` command-line option.
|
||||
|
||||
Version 1.8 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
|
@ -100,4 +100,4 @@ Compatibility: linux, darwin, cygwin, win32, aix, freebsd
|
||||
|
||||
Source: [`jc/parsers/xml.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/xml.py)
|
||||
|
||||
Version 1.9 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
Version 1.10 by Kelly Brazil (kellyjonbrazil@gmail.com)
|
||||
|
@ -33,7 +33,7 @@ Schema:
|
||||
"maximum_height": integer,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": integer,
|
||||
"resolution_height": integer,
|
||||
@ -82,7 +82,7 @@ Examples:
|
||||
"maximum_height": 32767,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": 1920,
|
||||
"resolution_height": 1080,
|
||||
@ -143,7 +143,7 @@ Examples:
|
||||
"maximum_height": 32767,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": 1920,
|
||||
"resolution_height": 1080,
|
||||
@ -199,7 +199,7 @@ Examples:
|
||||
### parse
|
||||
|
||||
```python
|
||||
def parse(data: str, raw: bool = False, quiet: bool = False) -> Dict
|
||||
def parse(data: str, raw: bool = False, quiet: bool = False) -> Response
|
||||
```
|
||||
|
||||
Main text parsing function
|
||||
@ -219,4 +219,4 @@ Compatibility: linux, darwin, cygwin, aix, freebsd
|
||||
|
||||
Source: [`jc/parsers/xrandr.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/xrandr.py)
|
||||
|
||||
Version 1.4 by Kevin Lyter (code (at) lyterk.com)
|
||||
Version 2.0 by Kevin Lyter (code (at) lyterk.com)
|
||||
|
12
jc/cli.py
12
jc/cli.py
@ -408,7 +408,7 @@ class JcCli():
|
||||
ensure_ascii=self.ascii_only
|
||||
)
|
||||
|
||||
if not self.mono:
|
||||
if not self.mono and PYGMENTS_INSTALLED:
|
||||
class JcStyle(Style):
|
||||
styles: CustomColorType = self.custom_colors
|
||||
|
||||
@ -540,13 +540,12 @@ class JcCli():
|
||||
if self.magic_run_command_str.startswith('/proc'):
|
||||
try:
|
||||
self.magic_found_parser = 'proc'
|
||||
filelist = shlex.split(self.magic_run_command_str)
|
||||
|
||||
# multiple proc files detected
|
||||
if ' ' in self.magic_run_command_str:
|
||||
if len(filelist) > 1:
|
||||
self.slurp = True
|
||||
multi_out: List[str] = []
|
||||
filelist = self.magic_run_command_str.split()
|
||||
filelist = [x.strip() for x in filelist]
|
||||
self.inputlist = filelist
|
||||
|
||||
for file in self.inputlist:
|
||||
@ -557,7 +556,7 @@ class JcCli():
|
||||
|
||||
# single proc file
|
||||
else:
|
||||
file = self.magic_run_command_str
|
||||
file = filelist[0]
|
||||
# self.magic_stdout = self.open_text_file('/Users/kelly/temp' + file)
|
||||
self.magic_stdout = self.open_text_file(file)
|
||||
|
||||
@ -861,6 +860,9 @@ class JcCli():
|
||||
self.set_mono()
|
||||
self.set_custom_colors()
|
||||
|
||||
if self.quiet:
|
||||
utils.CLI_QUIET = True
|
||||
|
||||
if self.verbose_debug:
|
||||
tracebackplus.enable(context=11) # type: ignore
|
||||
|
||||
|
@ -101,8 +101,8 @@ Examples:
|
||||
$ jc --pretty /proc/meminfo
|
||||
|
||||
Line Slicing:
|
||||
$ $ cat output.txt | jc 4:15 --parser # Parse from line 4 to 14
|
||||
with parser (zero-based)
|
||||
$ cat output.txt | jc 4:15 --parser # Parse from line 4 to 14
|
||||
with parser (zero-based)
|
||||
|
||||
Parser Documentation:
|
||||
$ jc --help --dig
|
||||
|
16
jc/lib.py
16
jc/lib.py
@ -10,7 +10,7 @@ from jc import appdirs
|
||||
from jc import utils
|
||||
|
||||
|
||||
__version__ = '1.25.0'
|
||||
__version__ = '1.25.1'
|
||||
|
||||
parsers: List[str] = [
|
||||
'acpi',
|
||||
@ -251,7 +251,8 @@ def _is_valid_parser_plugin(name: str, local_parsers_dir: str) -> bool:
|
||||
else:
|
||||
utils.warning_message([f'Not installing invalid parser plugin "{parser_mod_name}" at {local_parsers_dir}'])
|
||||
return False
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
utils.warning_message([f'Not installing parser plugin "{parser_mod_name}" at {local_parsers_dir} due to error: {e}'])
|
||||
return False
|
||||
return False
|
||||
|
||||
@ -324,7 +325,16 @@ def _get_parser(parser_mod_name: str) -> ModuleType:
|
||||
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}')
|
||||
mod = None
|
||||
|
||||
try:
|
||||
mod = importlib.import_module(f'{modpath}{parser_mod_name}')
|
||||
except Exception as e:
|
||||
mod = importlib.import_module(f'jc.parsers.disabled_parser')
|
||||
mod.__name__ = parser_mod_name
|
||||
utils.warning_message([f'"{parser_mod_name}" parser disabled due to error: {e}'])
|
||||
|
||||
return mod
|
||||
|
||||
def _parser_is_slurpable(parser: ModuleType) -> bool:
|
||||
"""
|
||||
|
23
jc/parsers/broken_parser.py
Normal file
23
jc/parsers/broken_parser.py
Normal file
@ -0,0 +1,23 @@
|
||||
"""jc - JSON Convert broken parser - for testing purposes only"""
|
||||
import non_existent_library
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.0'
|
||||
description = 'broken parser'
|
||||
author = 'N/A'
|
||||
author_email = 'N/A'
|
||||
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
|
||||
hidden = True
|
||||
|
||||
|
||||
__version__ = info.version
|
||||
|
||||
|
||||
def parse(
|
||||
data: str,
|
||||
raw: bool = False,
|
||||
quiet: bool = False
|
||||
) -> dict:
|
||||
"""Main text parsing function"""
|
||||
return {}
|
26
jc/parsers/disabled_parser.py
Normal file
26
jc/parsers/disabled_parser.py
Normal file
@ -0,0 +1,26 @@
|
||||
"""jc - JSON Convert disabled parser
|
||||
|
||||
This parser has been disabled due to an error in the parser code.
|
||||
"""
|
||||
from jc.exceptions import ParseError
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.0'
|
||||
description = 'Disabled parser'
|
||||
author = 'N/A'
|
||||
author_email = 'N/A'
|
||||
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
|
||||
hidden = True
|
||||
|
||||
|
||||
__version__ = info.version
|
||||
|
||||
|
||||
def parse(
|
||||
data: str,
|
||||
raw: bool = False,
|
||||
quiet: bool = False
|
||||
) -> dict:
|
||||
"""Main text parsing function"""
|
||||
raise ParseError('This parser is disabled.')
|
@ -75,7 +75,7 @@ import uuid
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '2.1'
|
||||
version = '2.2'
|
||||
description = 'INI file parser'
|
||||
author = 'Kelly Brazil'
|
||||
author_email = 'kellyjonbrazil@gmail.com'
|
||||
@ -87,14 +87,10 @@ class info():
|
||||
__version__ = info.version
|
||||
|
||||
|
||||
class MyDict(dict):
|
||||
def __setitem__(self, key, value):
|
||||
# convert None values to empty string
|
||||
if value is None:
|
||||
self[key] = ''
|
||||
|
||||
else:
|
||||
super().__setitem__(key, value)
|
||||
def _none_to_empty_string(data):
|
||||
if data is None:
|
||||
return ''
|
||||
return data
|
||||
|
||||
|
||||
def _process(proc_data):
|
||||
@ -110,13 +106,18 @@ def _process(proc_data):
|
||||
Dictionary representing the INI file.
|
||||
"""
|
||||
# remove quotation marks from beginning and end of values
|
||||
# and convert None to empty string
|
||||
for k, v in proc_data.items():
|
||||
if isinstance(v, dict):
|
||||
for key, value in v.items():
|
||||
v[key] = jc.utils.remove_quotes(value)
|
||||
value = _none_to_empty_string(value)
|
||||
value = jc.utils.remove_quotes(value)
|
||||
v[key] = value
|
||||
continue
|
||||
|
||||
proc_data[k] = jc.utils.remove_quotes(v)
|
||||
v = _none_to_empty_string(v)
|
||||
v = jc.utils.remove_quotes(v)
|
||||
proc_data[k] = v
|
||||
|
||||
return proc_data
|
||||
|
||||
@ -143,7 +144,6 @@ def parse(data, raw=False, quiet=False):
|
||||
if jc.utils.has_data(data):
|
||||
|
||||
ini_parser = configparser.ConfigParser(
|
||||
dict_type = MyDict,
|
||||
allow_no_value=True,
|
||||
interpolation=None,
|
||||
default_section=None,
|
||||
@ -175,4 +175,3 @@ def parse(data, raw=False, quiet=False):
|
||||
raw_output.update(temp_dict)
|
||||
|
||||
return raw_output if raw else _process(raw_output)
|
||||
|
||||
|
@ -44,6 +44,12 @@ Examples:
|
||||
...
|
||||
}
|
||||
"""
|
||||
import sys
|
||||
|
||||
# ugly hack because I accidentally shadowed the xml module from the
|
||||
# standard library with the xml parser. :(
|
||||
sys.path = [x for x in sys.path if 'jc/jc/parsers' not in x]
|
||||
|
||||
from typing import Dict, Union
|
||||
import plistlib
|
||||
import binascii
|
||||
@ -53,7 +59,7 @@ import jc.utils
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.1'
|
||||
version = '1.2'
|
||||
description = 'PLIST file parser'
|
||||
author = 'Kelly Brazil'
|
||||
author_email = 'kellyjonbrazil@gmail.com'
|
||||
|
@ -47,11 +47,11 @@ Specific Proc file parser names can be found with `jc -hh` or `jc -a`.
|
||||
|
||||
Schemas can also be found online at:
|
||||
|
||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_<name>
|
||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_<name>
|
||||
|
||||
For example:
|
||||
|
||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_meminfo
|
||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_meminfo
|
||||
|
||||
Examples:
|
||||
|
||||
|
@ -8,6 +8,16 @@ from typing import ByteString
|
||||
|
||||
__all__ = ["Edid"]
|
||||
|
||||
# EDID:
|
||||
# 00ffffffffffff004ca3523100000000
|
||||
# 0014010380221378eac8959e57549226
|
||||
# 0f505400000001010101010101010101
|
||||
# 010101010101381d56d4500016303020
|
||||
# 250058c2100000190000000f00000000
|
||||
# 000000000025d9066a00000000fe0053
|
||||
# 414d53554e470a204ca34154000000fe
|
||||
# 004c544e313536415432343430310018
|
||||
|
||||
|
||||
class Edid:
|
||||
"""Edid class
|
||||
@ -64,36 +74,39 @@ class Edid:
|
||||
|
||||
_ASPECT_RATIOS = {
|
||||
0b00: (16, 10),
|
||||
0b01: ( 4, 3),
|
||||
0b10: ( 5, 4),
|
||||
0b11: (16, 9),
|
||||
0b01: (4, 3),
|
||||
0b10: (5, 4),
|
||||
0b11: (16, 9),
|
||||
}
|
||||
|
||||
_RawEdid = namedtuple("RawEdid",
|
||||
("header",
|
||||
"manu_id",
|
||||
"prod_id",
|
||||
"serial_no",
|
||||
"manu_week",
|
||||
"manu_year",
|
||||
"edid_version",
|
||||
"edid_revision",
|
||||
"input_type",
|
||||
"width",
|
||||
"height",
|
||||
"gamma",
|
||||
"features",
|
||||
"color",
|
||||
"timings_supported",
|
||||
"timings_reserved",
|
||||
"timings_edid",
|
||||
"timing_1",
|
||||
"timing_2",
|
||||
"timing_3",
|
||||
"timing_4",
|
||||
"extension",
|
||||
"checksum")
|
||||
)
|
||||
_RawEdid = namedtuple(
|
||||
"RawEdid",
|
||||
(
|
||||
"header",
|
||||
"manu_id",
|
||||
"prod_id",
|
||||
"serial_no",
|
||||
"manu_week",
|
||||
"manu_year",
|
||||
"edid_version",
|
||||
"edid_revision",
|
||||
"input_type",
|
||||
"width",
|
||||
"height",
|
||||
"gamma",
|
||||
"features",
|
||||
"color",
|
||||
"timings_supported",
|
||||
"timings_reserved",
|
||||
"timings_edid",
|
||||
"timing_1",
|
||||
"timing_2",
|
||||
"timing_3",
|
||||
"timing_4",
|
||||
"extension",
|
||||
"checksum",
|
||||
),
|
||||
)
|
||||
|
||||
def __init__(self, edid: ByteString):
|
||||
self._parse_edid(edid)
|
||||
@ -109,18 +122,20 @@ class Edid:
|
||||
unpacked = struct.unpack(self._STRUCT_FORMAT, edid)
|
||||
raw_edid = self._RawEdid(*unpacked)
|
||||
|
||||
if raw_edid.header != b'\x00\xff\xff\xff\xff\xff\xff\x00':
|
||||
if raw_edid.header != b"\x00\xff\xff\xff\xff\xff\xff\x00":
|
||||
raise ValueError("Invalid header.")
|
||||
|
||||
self.raw = edid
|
||||
self.manufacturer_id = raw_edid.manu_id
|
||||
self.product = raw_edid.prod_id
|
||||
self.year = raw_edid.manu_year + 1990
|
||||
self.edid_version = "{:d}.{:d}".format(raw_edid.edid_version, raw_edid.edid_revision)
|
||||
self.edid_version = "{:d}.{:d}".format(
|
||||
raw_edid.edid_version, raw_edid.edid_revision
|
||||
)
|
||||
self.type = "digital" if (raw_edid.input_type & 0xFF) else "analog"
|
||||
self.width = float(raw_edid.width)
|
||||
self.height = float(raw_edid.height)
|
||||
self.gamma = (raw_edid.gamma+100)/100
|
||||
self.gamma = (raw_edid.gamma + 100) / 100
|
||||
self.dpms_standby = bool(raw_edid.features & 0xFF)
|
||||
self.dpms_suspend = bool(raw_edid.features & 0x7F)
|
||||
self.dpms_activeoff = bool(raw_edid.features & 0x3F)
|
||||
@ -132,22 +147,27 @@ class Edid:
|
||||
self.resolutions.append(self._TIMINGS[i])
|
||||
|
||||
for i in range(8):
|
||||
bytes_data = raw_edid.timings_edid[2*i:2*i+2]
|
||||
if bytes_data == b'\x01\x01':
|
||||
bytes_data = raw_edid.timings_edid[2 * i : 2 * i + 2]
|
||||
if bytes_data == b"\x01\x01":
|
||||
continue
|
||||
byte1, byte2 = bytes_data
|
||||
x_res = 8*(int(byte1)+31)
|
||||
aspect_ratio = self._ASPECT_RATIOS[(byte2>>6) & 0b11]
|
||||
y_res = int(x_res * aspect_ratio[1]/aspect_ratio[0])
|
||||
x_res = 8 * (int(byte1) + 31)
|
||||
aspect_ratio = self._ASPECT_RATIOS[(byte2 >> 6) & 0b11]
|
||||
y_res = int(x_res * aspect_ratio[1] / aspect_ratio[0])
|
||||
rate = (int(byte2) & 0b00111111) + 60.0
|
||||
self.resolutions.append((x_res, y_res, rate))
|
||||
|
||||
self.name = None
|
||||
self.serial = None
|
||||
|
||||
for timing_bytes in (raw_edid.timing_1, raw_edid.timing_2, raw_edid.timing_3, raw_edid.timing_4):
|
||||
for timing_bytes in (
|
||||
raw_edid.timing_1,
|
||||
raw_edid.timing_2,
|
||||
raw_edid.timing_3,
|
||||
raw_edid.timing_4,
|
||||
):
|
||||
# "other" descriptor
|
||||
if timing_bytes[0:2] == b'\x00\x00':
|
||||
if timing_bytes[0:2] == b"\x00\x00":
|
||||
timing_type = timing_bytes[3]
|
||||
if timing_type in (0xFF, 0xFE, 0xFC):
|
||||
buffer = timing_bytes[5:]
|
||||
|
@ -65,7 +65,7 @@ import jc.utils
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.8'
|
||||
version = '1.9'
|
||||
description = '`uptime` command parser'
|
||||
author = 'Kelly Brazil'
|
||||
author_email = 'kellyjonbrazil@gmail.com'
|
||||
@ -160,19 +160,27 @@ def parse(data, raw=False, quiet=False):
|
||||
jc.utils.input_type_check(data)
|
||||
|
||||
raw_output = {}
|
||||
cleandata = data.splitlines()
|
||||
|
||||
if jc.utils.has_data(data):
|
||||
time, _, *uptime, users, _, _, _, load_1m, load_5m, load_15m = cleandata[0].split()
|
||||
if 'users' in data:
|
||||
# standard uptime output
|
||||
time, _, *uptime, users, _, _, _, load_1m, load_5m, load_15m = data.split()
|
||||
|
||||
raw_output['time'] = time
|
||||
raw_output['uptime'] = ' '.join(uptime).rstrip(',')
|
||||
raw_output['users'] = users
|
||||
raw_output['load_1m'] = load_1m.rstrip(',')
|
||||
raw_output['load_5m'] = load_5m.rstrip(',')
|
||||
raw_output['load_15m'] = load_15m
|
||||
raw_output['time'] = time
|
||||
raw_output['uptime'] = ' '.join(uptime).rstrip(',')
|
||||
raw_output['users'] = users
|
||||
raw_output['load_1m'] = load_1m.rstrip(',')
|
||||
raw_output['load_5m'] = load_5m.rstrip(',')
|
||||
raw_output['load_15m'] = load_15m
|
||||
|
||||
if raw:
|
||||
return raw_output
|
||||
else:
|
||||
return _process(raw_output)
|
||||
else:
|
||||
# users information missing (e.g. busybox)
|
||||
time, _, *uptime, _, _, load_1m, load_5m, load_15m = data.split()
|
||||
|
||||
raw_output['time'] = time
|
||||
raw_output['uptime'] = ' '.join(uptime).rstrip(',')
|
||||
raw_output['load_1m'] = load_1m.rstrip(',')
|
||||
raw_output['load_5m'] = load_5m.rstrip(',')
|
||||
raw_output['load_15m'] = load_15m
|
||||
|
||||
return raw_output if raw else _process(raw_output)
|
||||
|
@ -73,15 +73,10 @@ Examples:
|
||||
import jc.utils
|
||||
from jc.exceptions import LibraryNotInstalled
|
||||
|
||||
try:
|
||||
import xmltodict
|
||||
except Exception:
|
||||
raise LibraryNotInstalled('The xmltodict library is not installed.')
|
||||
|
||||
|
||||
class info():
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = '1.9'
|
||||
version = '1.10'
|
||||
description = 'XML file parser'
|
||||
author = 'Kelly Brazil'
|
||||
author_email = 'kellyjonbrazil@gmail.com'
|
||||
@ -93,7 +88,7 @@ class info():
|
||||
__version__ = info.version
|
||||
|
||||
|
||||
def _process(proc_data, has_data=False):
|
||||
def _process(proc_data, has_data=False, xml_mod=None):
|
||||
"""
|
||||
Final processing to conform to the schema.
|
||||
|
||||
@ -105,16 +100,19 @@ def _process(proc_data, has_data=False):
|
||||
|
||||
Dictionary representing an XML document.
|
||||
"""
|
||||
if not xml_mod:
|
||||
raise LibraryNotInstalled('The xmltodict library is not installed.')
|
||||
|
||||
proc_output = []
|
||||
|
||||
if has_data:
|
||||
# standard output with @ prefix for attributes
|
||||
try:
|
||||
proc_output = xmltodict.parse(proc_data,
|
||||
proc_output = xml_mod.parse(proc_data,
|
||||
dict_constructor=dict,
|
||||
process_comments=True)
|
||||
except (ValueError, TypeError):
|
||||
proc_output = xmltodict.parse(proc_data, dict_constructor=dict)
|
||||
proc_output = xml_mod.parse(proc_data, dict_constructor=dict)
|
||||
|
||||
return proc_output
|
||||
|
||||
@ -133,6 +131,12 @@ def parse(data, raw=False, quiet=False):
|
||||
|
||||
Dictionary. Raw or processed structured data.
|
||||
"""
|
||||
xmltodict = None
|
||||
try:
|
||||
import xmltodict
|
||||
except Exception:
|
||||
raise LibraryNotInstalled('The xmltodict library is not installed.')
|
||||
|
||||
jc.utils.compatibility(__name__, info.compatible, quiet)
|
||||
jc.utils.input_type_check(data)
|
||||
|
||||
@ -156,4 +160,4 @@ def parse(data, raw=False, quiet=False):
|
||||
|
||||
return raw_output
|
||||
|
||||
return _process(data, has_data)
|
||||
return _process(data, has_data, xml_mod=xmltodict)
|
||||
|
@ -28,7 +28,7 @@ Schema:
|
||||
"maximum_height": integer,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": integer,
|
||||
"resolution_height": integer,
|
||||
@ -77,7 +77,7 @@ Examples:
|
||||
"maximum_height": 32767,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": 1920,
|
||||
"resolution_height": 1080,
|
||||
@ -138,7 +138,7 @@ Examples:
|
||||
"maximum_height": 32767,
|
||||
"devices": [
|
||||
{
|
||||
"modes": [
|
||||
"resolution_modes": [
|
||||
{
|
||||
"resolution_width": 1920,
|
||||
"resolution_height": 1080,
|
||||
@ -189,16 +189,27 @@ Examples:
|
||||
]
|
||||
}
|
||||
"""
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
import re
|
||||
from typing import Dict, List, Optional, Union
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
import jc.utils
|
||||
from jc.parsers.pyedid.edid import Edid
|
||||
from jc.parsers.pyedid.helpers.edid_helper import EdidHelper
|
||||
|
||||
Match = None
|
||||
try:
|
||||
# Added Python 3.7
|
||||
Match = re.Match
|
||||
except AttributeError:
|
||||
Match = type(re.match("", ""))
|
||||
|
||||
|
||||
class info:
|
||||
"""Provides parser metadata (version, author, etc.)"""
|
||||
version = "1.4"
|
||||
|
||||
version = "2.0"
|
||||
description = "`xrandr` command parser"
|
||||
author = "Kevin Lyter"
|
||||
author_email = "code (at) lyterk.com"
|
||||
@ -210,36 +221,10 @@ class info:
|
||||
|
||||
__version__ = info.version
|
||||
|
||||
# keep parsing state so we know which parsers have already tried the line
|
||||
# Structure is:
|
||||
# {
|
||||
# <line_string>: [
|
||||
# <parser_string>
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# Where <line_string> is the xrandr output line to be checked and <parser_string>
|
||||
# can contain "screen", "device", or "model"
|
||||
parse_state: Dict[str, List] = {}
|
||||
|
||||
|
||||
def _was_parsed(line: str, parser: str) -> bool:
|
||||
"""
|
||||
Check if entered parser has already parsed. If so return True.
|
||||
If not, return false and add the parser to the list for the line entry.
|
||||
"""
|
||||
if line in parse_state:
|
||||
if parser in parse_state[line]:
|
||||
return True
|
||||
|
||||
parse_state[line].append(parser)
|
||||
return False
|
||||
|
||||
parse_state[line] = [parser]
|
||||
return False
|
||||
|
||||
|
||||
# NOTE: When developing, comment out the try statement and catch block to get
|
||||
# TypedDict type hints and valid type errors.
|
||||
try:
|
||||
# Added in Python 3.8
|
||||
from typing import TypedDict
|
||||
|
||||
Frequency = TypedDict(
|
||||
@ -250,8 +235,8 @@ try:
|
||||
"is_preferred": bool,
|
||||
},
|
||||
)
|
||||
Mode = TypedDict(
|
||||
"Mode",
|
||||
ResolutionMode = TypedDict(
|
||||
"ResolutionMode",
|
||||
{
|
||||
"resolution_width": int,
|
||||
"resolution_height": int,
|
||||
@ -259,14 +244,15 @@ try:
|
||||
"frequencies": List[Frequency],
|
||||
},
|
||||
)
|
||||
Model = TypedDict(
|
||||
"Model",
|
||||
EdidModel = TypedDict(
|
||||
"EdidModel",
|
||||
{
|
||||
"name": str,
|
||||
"product_id": str,
|
||||
"serial_number": str,
|
||||
},
|
||||
)
|
||||
Props = Dict[str, Union[List[str], EdidModel]]
|
||||
Device = TypedDict(
|
||||
"Device",
|
||||
{
|
||||
@ -282,7 +268,8 @@ try:
|
||||
"offset_height": int,
|
||||
"dimension_width": int,
|
||||
"dimension_height": int,
|
||||
"modes": List[Mode],
|
||||
"props": Props,
|
||||
"resolution_modes": List[ResolutionMode],
|
||||
"rotation": str,
|
||||
"reflection": str,
|
||||
},
|
||||
@ -307,12 +294,13 @@ try:
|
||||
},
|
||||
)
|
||||
except ImportError:
|
||||
Screen = Dict[str, Union[int, str]]
|
||||
Device = Dict[str, Union[str, int, bool]]
|
||||
EdidModel = Dict[str, str]
|
||||
Props = Dict[str, Union[List[str], EdidModel]]
|
||||
Frequency = Dict[str, Union[float, bool]]
|
||||
Mode = Dict[str, Union[int, bool, List[Frequency]]]
|
||||
Model = Dict[str, str]
|
||||
Response = Dict[str, Union[Device, Mode, Screen]]
|
||||
ResolutionMode = Dict[str, Union[int, bool, List[Frequency]]]
|
||||
Device = Dict[str, Union[str, int, bool, List[ResolutionMode]]]
|
||||
Screen = Dict[str, Union[int, List[Device]]]
|
||||
Response = Dict[str, Screen]
|
||||
|
||||
|
||||
_screen_pattern = (
|
||||
@ -323,33 +311,6 @@ _screen_pattern = (
|
||||
)
|
||||
|
||||
|
||||
def _parse_screen(next_lines: List[str]) -> Optional[Screen]:
|
||||
next_line = next_lines.pop()
|
||||
|
||||
if _was_parsed(next_line, 'screen'):
|
||||
return None
|
||||
|
||||
result = re.match(_screen_pattern, next_line)
|
||||
if not result:
|
||||
next_lines.append(next_line)
|
||||
return None
|
||||
|
||||
raw_matches = result.groupdict()
|
||||
|
||||
screen: Screen = {"devices": []}
|
||||
for k, v in raw_matches.items():
|
||||
screen[k] = int(v)
|
||||
|
||||
while next_lines:
|
||||
device: Optional[Device] = _parse_device(next_lines)
|
||||
if not device:
|
||||
break
|
||||
else:
|
||||
screen["devices"].append(device)
|
||||
|
||||
return screen
|
||||
|
||||
|
||||
# eDP1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis)
|
||||
# 310mm x 170mm
|
||||
# regex101 demo link
|
||||
@ -365,25 +326,106 @@ _device_pattern = (
|
||||
+ r"( ?((?P<dimension_width>\d+)mm x (?P<dimension_height>\d+)mm)?)?"
|
||||
)
|
||||
|
||||
# 1920x1080i 60.03*+ 59.93
|
||||
# 1920x1080 60.00 + 50.00 59.94
|
||||
_resolution_mode_pattern = r"\s*(?P<resolution_width>\d+)x(?P<resolution_height>\d+)(?P<is_high_resolution>i)?\s+(?P<rest>.*)"
|
||||
_frequencies_pattern = r"(((?P<frequency>\d+\.\d+)(?P<star>\*| |)(?P<plus>\+?)?)+)"
|
||||
|
||||
def _parse_device(next_lines: List[str], quiet: bool = False) -> Optional[Device]:
|
||||
if not next_lines:
|
||||
return None
|
||||
|
||||
next_line = next_lines.pop()
|
||||
# Values sometimes appear on the same lines as the keys (CscMatrix), sometimes on the line
|
||||
# below (as with EDIDs), and sometimes both (CTM).
|
||||
# Capture the key line that way.
|
||||
#
|
||||
# CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
# 0 1
|
||||
# CscMatrix: 65536 0 0 0 0 65536 0 0 0 0 65536 0
|
||||
# EDID:
|
||||
# 00ffffffffffff0010ac33424c303541
|
||||
# 0f210104b53c22783eee95a3544c9926
|
||||
_prop_key_pattern = r"\s+(?P<key>[\w| |\-|_]+):\s?(?P<maybe_value>.*)"
|
||||
|
||||
if _was_parsed(next_line, 'device'):
|
||||
return None
|
||||
|
||||
result = re.match(_device_pattern, next_line)
|
||||
if not result:
|
||||
next_lines.append(next_line)
|
||||
return None
|
||||
class LineType(Enum):
|
||||
Screen = 1
|
||||
Device = 2
|
||||
ResolutionMode = 3
|
||||
PropKey = 4
|
||||
PropValue = 5
|
||||
Invalid = 6
|
||||
|
||||
matches = result.groupdict()
|
||||
|
||||
class _Line:
|
||||
"""Provide metadata about line to make handling it more simple across fn boundaries"""
|
||||
|
||||
def __init__(self, s: str, t: LineType, m: Match):
|
||||
self.s = s
|
||||
self.t = t
|
||||
self.m = m
|
||||
|
||||
@classmethod
|
||||
def categorize(cls, line: str) -> "_Line":
|
||||
"""Iterate through line char by char to see what type of line it is. Apply regexes for more distinctness. Save the regexes and return them for later processing."""
|
||||
i = 0
|
||||
tab_count = 0
|
||||
while True:
|
||||
try:
|
||||
c = line[i]
|
||||
except:
|
||||
# Really shouldn't be getting to the end of the line
|
||||
raise Exception(f"Reached end of line unexpectedly: '{line}'")
|
||||
|
||||
if not c.isspace():
|
||||
if tab_count == 0:
|
||||
screen_match = re.match(_screen_pattern, line)
|
||||
if screen_match:
|
||||
return cls(line, LineType.Screen, screen_match)
|
||||
|
||||
device_match = re.match(_device_pattern, line)
|
||||
if device_match:
|
||||
return cls(line, LineType.Device, device_match)
|
||||
else:
|
||||
break
|
||||
elif tab_count == 1:
|
||||
match = re.match(_prop_key_pattern, line)
|
||||
if match:
|
||||
return cls(line, LineType.PropKey, match)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
match = re.match(r"\s+(.*)\s+", line)
|
||||
if match:
|
||||
return cls(line, LineType.PropValue, match)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
if c == " ":
|
||||
match = re.match(_resolution_mode_pattern, line)
|
||||
if match:
|
||||
return cls(line, LineType.ResolutionMode, match)
|
||||
else:
|
||||
break
|
||||
elif c == "\t":
|
||||
tab_count += 1
|
||||
i += 1
|
||||
raise Exception(f"Line could not be categorized: '{line}'")
|
||||
|
||||
|
||||
def _parse_screen(line: _Line) -> Screen:
|
||||
d = line.m.groupdict()
|
||||
|
||||
screen: Screen = {"devices": []} # type: ignore # Will be populated, but not immediately.
|
||||
for k, v in d.items():
|
||||
screen[k] = int(v)
|
||||
|
||||
return screen
|
||||
|
||||
|
||||
def _parse_device(line: _Line) -> Device:
|
||||
matches = line.m.groupdict()
|
||||
|
||||
device: Device = {
|
||||
"modes": [],
|
||||
"props": defaultdict(list),
|
||||
"resolution_modes": [],
|
||||
"is_connected": matches["is_connected"] == "connected",
|
||||
"is_primary": matches["is_primary"] is not None
|
||||
and len(matches["is_primary"]) > 0,
|
||||
@ -403,97 +445,20 @@ def _parse_device(next_lines: List[str], quiet: bool = False) -> Optional[Device
|
||||
if v:
|
||||
device[k] = int(v)
|
||||
except ValueError:
|
||||
if not quiet:
|
||||
jc.utils.warning_message(
|
||||
[f"{next_line} : {k} - {v} is not int-able"]
|
||||
)
|
||||
raise Exception([f"{line.s} : {k} - {v} is not int-able"])
|
||||
|
||||
model: Optional[Model] = _parse_model(next_lines, quiet)
|
||||
if model:
|
||||
device["model_name"] = model["name"]
|
||||
device["product_id"] = model["product_id"]
|
||||
device["serial_number"] = model["serial_number"]
|
||||
|
||||
while next_lines:
|
||||
next_line = next_lines.pop()
|
||||
next_mode: Optional[Mode] = _parse_mode(next_line)
|
||||
if next_mode:
|
||||
device["modes"].append(next_mode)
|
||||
else:
|
||||
if re.match(_device_pattern, next_line):
|
||||
next_lines.append(next_line)
|
||||
break
|
||||
return device
|
||||
|
||||
|
||||
# EDID:
|
||||
# 00ffffffffffff004ca3523100000000
|
||||
# 0014010380221378eac8959e57549226
|
||||
# 0f505400000001010101010101010101
|
||||
# 010101010101381d56d4500016303020
|
||||
# 250058c2100000190000000f00000000
|
||||
# 000000000025d9066a00000000fe0053
|
||||
# 414d53554e470a204ca34154000000fe
|
||||
# 004c544e313536415432343430310018
|
||||
_edid_head_pattern = r"\s*EDID:\s*"
|
||||
_edid_line_pattern = r"\s*(?P<edid_line>[0-9a-fA-F]{32})\s*"
|
||||
|
||||
|
||||
def _parse_model(next_lines: List[str], quiet: bool = False) -> Optional[Model]:
|
||||
if not next_lines:
|
||||
return None
|
||||
|
||||
next_line = next_lines.pop()
|
||||
|
||||
if _was_parsed(next_line, 'model'):
|
||||
return None
|
||||
|
||||
if not re.match(_edid_head_pattern, next_line):
|
||||
next_lines.append(next_line)
|
||||
return None
|
||||
|
||||
edid_hex_value = ""
|
||||
|
||||
while next_lines:
|
||||
next_line = next_lines.pop()
|
||||
result = re.match(_edid_line_pattern, next_line)
|
||||
|
||||
if not result:
|
||||
next_lines.append(next_line)
|
||||
break
|
||||
|
||||
matches = result.groupdict()
|
||||
edid_hex_value += matches["edid_line"]
|
||||
|
||||
edid = Edid(EdidHelper.hex2bytes(edid_hex_value))
|
||||
|
||||
model: Model = {
|
||||
"name": edid.name or "Generic",
|
||||
"product_id": str(edid.product),
|
||||
"serial_number": str(edid.serial),
|
||||
}
|
||||
return model
|
||||
|
||||
|
||||
# 1920x1080i 60.03*+ 59.93
|
||||
# 1920x1080 60.00 + 50.00 59.94
|
||||
_mode_pattern = r"\s*(?P<resolution_width>\d+)x(?P<resolution_height>\d+)(?P<is_high_resolution>i)?\s+(?P<rest>.*)"
|
||||
_frequencies_pattern = r"(((?P<frequency>\d+\.\d+)(?P<star>\*| |)(?P<plus>\+?)?)+)"
|
||||
|
||||
|
||||
def _parse_mode(line: str) -> Optional[Mode]:
|
||||
result = re.match(_mode_pattern, line)
|
||||
def _parse_resolution_mode(line: _Line) -> ResolutionMode:
|
||||
frequencies: List[Frequency] = []
|
||||
|
||||
if not result:
|
||||
return None
|
||||
|
||||
d = result.groupdict()
|
||||
d = line.m.groupdict()
|
||||
resolution_width = int(d["resolution_width"])
|
||||
resolution_height = int(d["resolution_height"])
|
||||
is_high_resolution = d["is_high_resolution"] is not None
|
||||
|
||||
mode: Mode = {
|
||||
mode: ResolutionMode = {
|
||||
"resolution_width": resolution_width,
|
||||
"resolution_height": resolution_height,
|
||||
"is_high_resolution": is_high_resolution,
|
||||
@ -518,7 +483,45 @@ def _parse_mode(line: str) -> Optional[Mode]:
|
||||
return mode
|
||||
|
||||
|
||||
def parse(data: str, raw: bool = False, quiet: bool = False) -> Dict:
|
||||
def _parse_props(index: int, line: _Line, lines: List[str]) -> Tuple[int, Props]:
|
||||
tmp_props: Dict[str, List[str]] = {}
|
||||
key = ""
|
||||
while index <= len(lines):
|
||||
if line.t == LineType.PropKey:
|
||||
d = line.m.groupdict()
|
||||
# See _prop_key_pattern
|
||||
key = d["key"]
|
||||
maybe_value = d["maybe_value"]
|
||||
if not maybe_value:
|
||||
tmp_props[key] = []
|
||||
else:
|
||||
tmp_props[key] = [maybe_value]
|
||||
elif line.t == LineType.PropValue:
|
||||
tmp_props[key].append(line.s.strip())
|
||||
else:
|
||||
# We've gone past our props and need to ascend
|
||||
index = index - 1
|
||||
break
|
||||
index += 1
|
||||
try:
|
||||
line = _Line.categorize(lines[index])
|
||||
except:
|
||||
pass
|
||||
|
||||
props: Props = {}
|
||||
if "EDID" in tmp_props:
|
||||
edid = Edid(EdidHelper.hex2bytes("".join(tmp_props["EDID"])))
|
||||
model: EdidModel = {
|
||||
"name": edid.name or "Generic",
|
||||
"product_id": str(edid.product),
|
||||
"serial_number": str(edid.serial),
|
||||
}
|
||||
props["EdidModel"] = model
|
||||
|
||||
return index, {**tmp_props, **props}
|
||||
|
||||
|
||||
def parse(data: str, raw: bool = False, quiet: bool = False) -> Response:
|
||||
"""
|
||||
Main text parsing function
|
||||
|
||||
@ -535,15 +538,34 @@ def parse(data: str, raw: bool = False, quiet: bool = False) -> Dict:
|
||||
jc.utils.compatibility(__name__, info.compatible, quiet)
|
||||
jc.utils.input_type_check(data)
|
||||
|
||||
linedata = data.splitlines()
|
||||
linedata.reverse() # For popping
|
||||
result: Dict = {}
|
||||
index = 0
|
||||
lines = data.splitlines()
|
||||
screen, device = None, None
|
||||
|
||||
result: Response = {"screens": []}
|
||||
if jc.utils.has_data(data):
|
||||
result = {"screens": []}
|
||||
while linedata:
|
||||
screen = _parse_screen(linedata)
|
||||
if screen:
|
||||
while index < len(lines):
|
||||
line = _Line.categorize(lines[index])
|
||||
if line.t == LineType.Screen:
|
||||
screen = _parse_screen(line)
|
||||
result["screens"].append(screen)
|
||||
elif line.t == LineType.Device:
|
||||
device = _parse_device(line)
|
||||
if not screen:
|
||||
raise Exception("There should be an identifiable screen")
|
||||
screen["devices"].append(device)
|
||||
elif line.t == LineType.ResolutionMode:
|
||||
resolution_mode = _parse_resolution_mode(line)
|
||||
if not device:
|
||||
raise Exception("Undefined device")
|
||||
device["resolution_modes"].append(resolution_mode)
|
||||
elif line.t == LineType.PropKey:
|
||||
# Props needs to be state aware, it owns the index.
|
||||
ix, props = _parse_props(index, line, lines)
|
||||
index = ix
|
||||
if not device:
|
||||
raise Exception("Undefined device")
|
||||
device["props"] = props
|
||||
index += 1
|
||||
|
||||
return result
|
||||
|
@ -12,6 +12,7 @@ from functools import lru_cache
|
||||
from typing import Any, List, Dict, Iterable, Union, Optional, TextIO
|
||||
from .jc_types import TimeStampFormatType
|
||||
|
||||
CLI_QUIET = False
|
||||
|
||||
def _asciify(string: str) -> str:
|
||||
"""
|
||||
@ -62,6 +63,9 @@ def warning_message(message_lines: List[str]) -> None:
|
||||
|
||||
None - just prints output to STDERR
|
||||
"""
|
||||
if CLI_QUIET:
|
||||
return
|
||||
|
||||
# this is for backwards compatibility with existing custom parsers
|
||||
if isinstance(message_lines, str):
|
||||
message_lines = [message_lines]
|
||||
|
2
man/jc.1
2
man/jc.1
@ -1,4 +1,4 @@
|
||||
.TH jc 1 2024-02-05 1.25.0 "JSON Convert"
|
||||
.TH jc 1 2024-02-12 1.25.1 "JSON Convert"
|
||||
.SH NAME
|
||||
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types,
|
||||
and strings
|
||||
|
7
runtests-missing-libs.sh
Executable file
7
runtests-missing-libs.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# system should be in "America/Los_Angeles" timezone for all tests to pass
|
||||
# ensure no local plugin parsers are installed for all tests to pass
|
||||
|
||||
pip uninstall pygments ruamel.yaml xmltodict --yes
|
||||
python3 -m unittest -v
|
||||
pip install pygments ruamel.yaml xmltodict
|
2
setup.py
2
setup.py
@ -5,7 +5,7 @@ with open('README.md', 'r') as f:
|
||||
|
||||
setuptools.setup(
|
||||
name='jc',
|
||||
version='1.25.0',
|
||||
version='1.25.1',
|
||||
author='Kelly Brazil',
|
||||
author_email='kellyjonbrazil@gmail.com',
|
||||
description='Converts the output of popular command-line tools and file-types to JSON.',
|
||||
|
1
tests/fixtures/generic/ini-dup-mariadb.json
vendored
Normal file
1
tests/fixtures/generic/ini-dup-mariadb.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"server":{},"mysqld":{"user":["mysql"],"pid_file":["/var/run/mysqld/mysqld.pid"],"port":["3306"],"basedir":["/usr"],"datadir":["/var/lib/mysql"],"tmpdir":["/tmp"],"lc_messages_dir":["/usr/share/mysql"],"skip_external_locking":[""],"bind_address":["127.0.0.1"],"key_buffer_size":["16M"],"max_allowed_packet":["64M"],"thread_stack":["192K"],"thread_cache_size":["8"],"myisam_recover_options":["BACKUP"],"max_connections":["80"],"max_user_connections":["0"],"table_cache":["64"],"thread_concurrency":["10"],"open_files_limit":["122880"],"table_open_cache":["6000"],"tmp_table_size":["32M"],"join_buffer_size":["8M"],"max_heap_table_size":["32M"],"query_cache_type":["0"],"query_cache_limit":["0"],"query_cache_size":["0"],"log_error":["/var/log/mysql/error.log"],"expire_logs_days":["10"],"max_binlog_size":["100M"],"innodb_buffer_pool_size":["1G"],"innodb_log_file_size":["256M"],"character_set_server":["utf8mb4"],"collation_server":["utf8mb4_general_ci"],"ignore_db_dir":["lost+found"]},"embedded":{},"mariadb":{"performance_schema":["ON"],"performance_schema_instrument":["stage/%=ON"],"performance_schema_consumer_events_stages_current":["ON"],"performance_schema_consumer_events_stages_history":["ON"],"performance_schema_consumer_events_stages_history_long":["ON"]},"mariadb-10.1":{}}
|
151
tests/fixtures/generic/ini-mariadb.ini
vendored
Normal file
151
tests/fixtures/generic/ini-mariadb.ini
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
# Ansible managed
|
||||
# These groups are read by MariaDB server.
|
||||
# Use it for options that only the server (but not clients) should see
|
||||
#
|
||||
# See the examples of server my.cnf files in /usr/share/mysql/
|
||||
#
|
||||
|
||||
# this is read by the standalone daemon and embedded servers
|
||||
[server]
|
||||
|
||||
# this is only for the mysqld standalone daemon
|
||||
[mysqld]
|
||||
|
||||
#
|
||||
# * Basic Settings
|
||||
#
|
||||
user = mysql
|
||||
pid_file = /var/run/mysqld/mysqld.pid
|
||||
port = 3306
|
||||
basedir = /usr
|
||||
datadir = /var/lib/mysql
|
||||
tmpdir = /tmp
|
||||
lc_messages_dir = /usr/share/mysql
|
||||
skip_external_locking
|
||||
|
||||
# Instead of skip-networking the default is now to listen only on
|
||||
# localhost which is more compatible and is not less secure.
|
||||
bind_address = 127.0.0.1
|
||||
|
||||
#
|
||||
# * Fine Tuning
|
||||
#
|
||||
key_buffer_size = 16M
|
||||
max_allowed_packet = 64M
|
||||
thread_stack = 192K
|
||||
thread_cache_size = 8
|
||||
# This replaces the startup script and checks MyISAM tables if needed
|
||||
# the first time they are touched
|
||||
myisam_recover_options = BACKUP
|
||||
max_connections = 80
|
||||
max_user_connections = 0
|
||||
table_cache = 64
|
||||
thread_concurrency = 10
|
||||
open_files_limit = 122880
|
||||
table_open_cache = 6000
|
||||
tmp_table_size = 32M
|
||||
join_buffer_size = 8M
|
||||
max_heap_table_size = 32M
|
||||
|
||||
#
|
||||
# * Query Cache Configuration
|
||||
#
|
||||
# Disabled by default in MariaDB >= 10.1.7 see:
|
||||
# https://mariadb.com/kb/en/query-cache/
|
||||
query_cache_type = 0
|
||||
query_cache_limit = 0
|
||||
query_cache_size = 0
|
||||
|
||||
#
|
||||
# * Logging and Replication
|
||||
#
|
||||
# Both location gets rotated by the cronjob.
|
||||
# Be aware that this log type is a performance killer.
|
||||
# As of 5.1 you can enable the log at runtime!
|
||||
#general_log_file = /var/log/mysql/mysql.log
|
||||
#general_log = 1
|
||||
#
|
||||
# Error log - should be very few entries.
|
||||
#
|
||||
log_error = /var/log/mysql/error.log
|
||||
#
|
||||
# Enable the slow query log to see queries with especially long duration
|
||||
#slow_query_log_file = /var/log/mysql/mariadb-slow.log
|
||||
#long_query_time = 10
|
||||
#log_slow_rate_limit = 1000
|
||||
#log_slow_verbosity = query_plan
|
||||
#log-queries-not-using-indexes
|
||||
#
|
||||
# The following can be used as easy to replay backup logs or for replication.
|
||||
# note: if you are setting up a replication slave, see README.Debian about
|
||||
# other settings you may need to change.
|
||||
#server-id = 1
|
||||
#log_bin = /var/log/mysql/mysql-bin.log
|
||||
expire_logs_days = 10
|
||||
max_binlog_size = 100M
|
||||
#binlog_do_db = include_database_name
|
||||
#binlog_ignore_db = exclude_database_name
|
||||
|
||||
#
|
||||
# * InnoDB
|
||||
#
|
||||
# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
|
||||
# Read the manual for more InnoDB related options. There are many!
|
||||
innodb_buffer_pool_size = 1G
|
||||
innodb_log_file_size = 256M
|
||||
|
||||
# * Security Features
|
||||
#
|
||||
# Read the manual, too, if you want chroot!
|
||||
# chroot = /var/lib/mysql/
|
||||
#
|
||||
# For generating SSL certificates you can use for example the GUI tool "tinyca".
|
||||
#
|
||||
# ssl-ca=/etc/mysql/cacert.pem
|
||||
# ssl-cert=/etc/mysql/server-cert.pem
|
||||
# ssl-key=/etc/mysql/server-key.pem
|
||||
#
|
||||
# Accept only connections using the latest and most secure TLS protocol version.
|
||||
# ..when MariaDB is compiled with OpenSSL:
|
||||
# ssl-cipher=TLSv1.2
|
||||
# ..when MariaDB is compiled with YaSSL (default in Debian):
|
||||
# ssl=on
|
||||
|
||||
#
|
||||
# * Character sets
|
||||
#
|
||||
# MySQL/MariaDB default is Latin1, but in Debian we rather default to the full
|
||||
# utf8 4-byte character set. See also client.cnf
|
||||
#
|
||||
character_set_server = utf8mb4
|
||||
collation_server = utf8mb4_general_ci
|
||||
ignore_db_dir = lost+found
|
||||
|
||||
#
|
||||
# * Unix socket authentication plugin is built-in since 10.0.22-6
|
||||
#
|
||||
# Needed so the root database user can authenticate without a password but
|
||||
# only when running as the unix root user.
|
||||
#
|
||||
# Also available for other users if required.
|
||||
# See https://mariadb.com/kb/en/unix_socket-authentication-plugin/
|
||||
|
||||
# this is only for embedded server
|
||||
[embedded]
|
||||
|
||||
# This group is only read by MariaDB servers, not by MySQL.
|
||||
# If you use the same .cnf file for MySQL and MariaDB,
|
||||
# you can put MariaDB-only options here
|
||||
[mariadb]
|
||||
# https://mariadb.com/kb/en/library/performance-schema-overview/
|
||||
performance_schema=ON
|
||||
performance_schema_instrument='stage/%=ON'
|
||||
performance_schema_consumer_events_stages_current=ON
|
||||
performance_schema_consumer_events_stages_history=ON
|
||||
performance_schema_consumer_events_stages_history_long=ON
|
||||
|
||||
# This group is only read by MariaDB-10.1 servers.
|
||||
# If you use the same .cnf file for MariaDB of different versions,
|
||||
# use this group for options that older servers don't understand
|
||||
[mariadb-10.1]
|
||||
# vim: syntax=dosini
|
1
tests/fixtures/generic/ini-mariadb.json
vendored
Normal file
1
tests/fixtures/generic/ini-mariadb.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"server":{},"mysqld":{"user":"mysql","pid_file":"/var/run/mysqld/mysqld.pid","port":"3306","basedir":"/usr","datadir":"/var/lib/mysql","tmpdir":"/tmp","lc_messages_dir":"/usr/share/mysql","skip_external_locking":"","bind_address":"127.0.0.1","key_buffer_size":"16M","max_allowed_packet":"64M","thread_stack":"192K","thread_cache_size":"8","myisam_recover_options":"BACKUP","max_connections":"80","max_user_connections":"0","table_cache":"64","thread_concurrency":"10","open_files_limit":"122880","table_open_cache":"6000","tmp_table_size":"32M","join_buffer_size":"8M","max_heap_table_size":"32M","query_cache_type":"0","query_cache_limit":"0","query_cache_size":"0","log_error":"/var/log/mysql/error.log","expire_logs_days":"10","max_binlog_size":"100M","innodb_buffer_pool_size":"1G","innodb_log_file_size":"256M","character_set_server":"utf8mb4","collation_server":"utf8mb4_general_ci","ignore_db_dir":"lost+found"},"embedded":{},"mariadb":{"performance_schema":"ON","performance_schema_instrument":"stage/%=ON","performance_schema_consumer_events_stages_current":"ON","performance_schema_consumer_events_stages_history":"ON","performance_schema_consumer_events_stages_history_long":"ON"},"mariadb-10.1":{}}
|
138
tests/fixtures/generic/xrandr_issue_525.out
vendored
Normal file
138
tests/fixtures/generic/xrandr_issue_525.out
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384
|
||||
eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm
|
||||
EDID:
|
||||
00ffffffffffff0006af3d5700000000
|
||||
001c0104a51f1178022285a5544d9a27
|
||||
0e505400000001010101010101010101
|
||||
010101010101b43780a070383e401010
|
||||
350035ae100000180000000f00000000
|
||||
00000000000000000020000000fe0041
|
||||
554f0a202020202020202020000000fe
|
||||
004231343048414e30352e37200a0070
|
||||
scaling mode: Full aspect
|
||||
supported: Full, Center, Full aspect
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
panel orientation: Normal
|
||||
supported: Normal, Upside Down, Left Side Up, Right Side Up
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 95
|
||||
supported: 95
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
1920x1080 60.03*+ 60.01 59.97 59.96 59.93
|
||||
1680x1050 59.95 59.88
|
||||
1400x1050 59.98
|
||||
1600x900 59.99 59.94 59.95 59.82
|
||||
1280x1024 60.02
|
||||
1400x900 59.96 59.88
|
||||
1280x960 60.00
|
||||
1440x810 60.00 59.97
|
||||
1368x768 59.88 59.85
|
||||
1280x800 59.99 59.97 59.81 59.91
|
||||
1280x720 60.00 59.99 59.86 59.74
|
||||
1024x768 60.04 60.00
|
||||
960x720 60.00
|
||||
928x696 60.05
|
||||
896x672 60.01
|
||||
1024x576 59.95 59.96 59.90 59.82
|
||||
960x600 59.93 60.00
|
||||
960x540 59.96 59.99 59.63 59.82
|
||||
800x600 60.00 60.32 56.25
|
||||
840x525 60.01 59.88
|
||||
864x486 59.92 59.57
|
||||
700x525 59.98
|
||||
800x450 59.95 59.82
|
||||
640x512 60.02
|
||||
700x450 59.96 59.88
|
||||
640x480 60.00 59.94
|
||||
720x405 59.51 58.99
|
||||
684x384 59.88 59.85
|
||||
640x400 59.88 59.98
|
||||
640x360 59.86 59.83 59.84 59.32
|
||||
512x384 60.00
|
||||
512x288 60.00 59.92
|
||||
480x270 59.63 59.82
|
||||
400x300 60.32 56.34
|
||||
432x243 59.92 59.57
|
||||
320x240 60.05
|
||||
360x202 59.51 59.13
|
||||
320x180 59.84 59.32
|
||||
DP-1 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
subconnector: Unknown
|
||||
supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 103
|
||||
supported: 103
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
HDMI-1 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
max bpc: 12
|
||||
range: (8, 12)
|
||||
content type: No Data
|
||||
supported: No Data, Graphics, Photo, Cinema, Game
|
||||
Colorspace: Default
|
||||
supported: Default, SMPTE_170M_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, opRGB, BT2020_CYCC, BT2020_RGB, BT2020_YCC, DCI-P3_RGB_D65, DCI-P3_RGB_Theater
|
||||
aspect ratio: Automatic
|
||||
supported: Automatic, 4:3, 16:9
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 113
|
||||
supported: 113
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
DP-2 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
subconnector: Unknown
|
||||
supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 119
|
||||
supported: 119
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
138
tests/fixtures/generic/xrandr_properties_1.out
vendored
Normal file
138
tests/fixtures/generic/xrandr_properties_1.out
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384
|
||||
eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm
|
||||
EDID:
|
||||
00ffffffffffff0006af3d5700000000
|
||||
001c0104a51f1178022285a5544d9a27
|
||||
0e505400000001010101010101010101
|
||||
010101010101b43780a070383e401010
|
||||
350035ae100000180000000f00000000
|
||||
00000000000000000020000000fe0041
|
||||
554f0a202020202020202020000000fe
|
||||
004231343048414e30352e37200a0070
|
||||
scaling mode: Full aspect
|
||||
supported: Full, Center, Full aspect
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
panel orientation: Normal
|
||||
supported: Normal, Upside Down, Left Side Up, Right Side Up
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 95
|
||||
supported: 95
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
1920x1080 60.03*+ 60.01 59.97 59.96 59.93
|
||||
1680x1050 59.95 59.88
|
||||
1400x1050 59.98
|
||||
1600x900 59.99 59.94 59.95 59.82
|
||||
1280x1024 60.02
|
||||
1400x900 59.96 59.88
|
||||
1280x960 60.00
|
||||
1440x810 60.00 59.97
|
||||
1368x768 59.88 59.85
|
||||
1280x800 59.99 59.97 59.81 59.91
|
||||
1280x720 60.00 59.99 59.86 59.74
|
||||
1024x768 60.04 60.00
|
||||
960x720 60.00
|
||||
928x696 60.05
|
||||
896x672 60.01
|
||||
1024x576 59.95 59.96 59.90 59.82
|
||||
960x600 59.93 60.00
|
||||
960x540 59.96 59.99 59.63 59.82
|
||||
800x600 60.00 60.32 56.25
|
||||
840x525 60.01 59.88
|
||||
864x486 59.92 59.57
|
||||
700x525 59.98
|
||||
800x450 59.95 59.82
|
||||
640x512 60.02
|
||||
700x450 59.96 59.88
|
||||
640x480 60.00 59.94
|
||||
720x405 59.51 58.99
|
||||
684x384 59.88 59.85
|
||||
640x400 59.88 59.98
|
||||
640x360 59.86 59.83 59.84 59.32
|
||||
512x384 60.00
|
||||
512x288 60.00 59.92
|
||||
480x270 59.63 59.82
|
||||
400x300 60.32 56.34
|
||||
432x243 59.92 59.57
|
||||
320x240 60.05
|
||||
360x202 59.51 59.13
|
||||
320x180 59.84 59.32
|
||||
DP-1 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
subconnector: Unknown
|
||||
supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 103
|
||||
supported: 103
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
HDMI-1 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
max bpc: 12
|
||||
range: (8, 12)
|
||||
content type: No Data
|
||||
supported: No Data, Graphics, Photo, Cinema, Game
|
||||
Colorspace: Default
|
||||
supported: Default, SMPTE_170M_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, opRGB, BT2020_CYCC, BT2020_RGB, BT2020_YCC, DCI-P3_RGB_D65, DCI-P3_RGB_Theater
|
||||
aspect ratio: Automatic
|
||||
supported: Automatic, 4:3, 16:9
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 113
|
||||
supported: 113
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
||||
DP-2 disconnected (normal left inverted right x axis y axis)
|
||||
HDCP Content Type: HDCP Type0
|
||||
supported: HDCP Type0, HDCP Type1
|
||||
Content Protection: Undesired
|
||||
supported: Undesired, Desired, Enabled
|
||||
Colorspace: Default
|
||||
supported: Default, RGB_Wide_Gamut_Fixed_Point, RGB_Wide_Gamut_Floating_Point, opRGB, DCI-P3_RGB_D65, BT2020_RGB, BT601_YCC, BT709_YCC, XVYCC_601, XVYCC_709, SYCC_601, opYCC_601, BT2020_CYCC, BT2020_YCC
|
||||
max bpc: 12
|
||||
range: (6, 12)
|
||||
Broadcast RGB: Automatic
|
||||
supported: Automatic, Full, Limited 16:235
|
||||
audio: auto
|
||||
supported: force-dvi, off, auto, on
|
||||
subconnector: Unknown
|
||||
supported: Unknown, VGA, DVI-D, HDMI, DP, Wireless, Native
|
||||
link-status: Good
|
||||
supported: Good, Bad
|
||||
CTM: 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0
|
||||
0 1
|
||||
CONNECTOR_ID: 119
|
||||
supported: 119
|
||||
non-desktop: 0
|
||||
range: (0, 1)
|
@ -21,6 +21,9 @@ class MyTests(unittest.TestCase):
|
||||
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()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-mariadb.ini'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_mariadb = f.read()
|
||||
|
||||
# output
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-test.json'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_test_json = json.loads(f.read())
|
||||
@ -34,6 +37,9 @@ class MyTests(unittest.TestCase):
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-single-quote.json'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_single_quote_json = json.loads(f.read())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-mariadb.json'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_mariadb_json = json.loads(f.read())
|
||||
|
||||
|
||||
def test_ini_nodata(self):
|
||||
"""
|
||||
@ -53,6 +59,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini.parse(self.generic_ini_iptelserver, quiet=True), self.generic_ini_iptelserver_json)
|
||||
|
||||
def test_ini_mariadb(self):
|
||||
"""
|
||||
Test the mariadb ini file
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini.parse(self.generic_ini_mariadb, quiet=True), self.generic_ini_mariadb_json)
|
||||
|
||||
def test_ini_duplicate_keys(self):
|
||||
"""
|
||||
Test input that contains duplicate keys. Only the last value should be used.
|
||||
@ -104,6 +116,15 @@ key5 = "quoted"
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini.parse(self.generic_ini_single_quote, quiet=True), self.generic_ini_single_quote_json)
|
||||
|
||||
def test_ini_single_key_no_value(self):
|
||||
"""
|
||||
Test ini file with a single item with no value. This caused issues in jc v.1.25.0
|
||||
"""
|
||||
data = '''[data]
|
||||
novalue
|
||||
'''
|
||||
expected = {"data":{"novalue":""}}
|
||||
self.assertEqual(jc.parsers.ini.parse(data, quiet=True), expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -21,6 +21,9 @@ class MyTests(unittest.TestCase):
|
||||
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()
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-mariadb.ini'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_mariadb = 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())
|
||||
@ -34,6 +37,9 @@ class MyTests(unittest.TestCase):
|
||||
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())
|
||||
|
||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-dup-mariadb.json'), 'r', encoding='utf-8') as f:
|
||||
generic_ini_dup_mariadb_json = json.loads(f.read())
|
||||
|
||||
|
||||
def test_ini_dup_nodata(self):
|
||||
"""
|
||||
@ -53,6 +59,12 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(self.generic_ini_iptelserver, quiet=True), self.generic_ini_dup_iptelserver_json)
|
||||
|
||||
def test_ini_dup_mariadb(self):
|
||||
"""
|
||||
Test the mariadb ini file
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(self.generic_ini_mariadb, quiet=True), self.generic_ini_dup_mariadb_json)
|
||||
|
||||
def test_ini_dup_duplicate_keys(self):
|
||||
"""
|
||||
Test input that contains duplicate keys.
|
||||
@ -94,7 +106,15 @@ key5 = "quoted"
|
||||
"""
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(self.generic_ini_single_quote, quiet=True), self.generic_ini_dup_single_quote_json)
|
||||
|
||||
|
||||
def test_ini_dup_single_key_no_value(self):
|
||||
"""
|
||||
Test ini file with a single item with no value.
|
||||
"""
|
||||
data = '''[data]
|
||||
novalue
|
||||
'''
|
||||
expected = {"data":{"novalue":[""]}}
|
||||
self.assertEqual(jc.parsers.ini_dup.parse(data, quiet=True), expected)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -1,12 +1,20 @@
|
||||
import os
|
||||
import unittest
|
||||
from datetime import datetime, timezone
|
||||
|
||||
try:
|
||||
import pygments
|
||||
from pygments.token import (Name, Number, String, Keyword)
|
||||
PYGMENTS_INSTALLED=True
|
||||
except ModuleNotFoundError:
|
||||
except:
|
||||
PYGMENTS_INSTALLED=False
|
||||
|
||||
try:
|
||||
import ruamel.yaml
|
||||
RUAMELYAML_INSTALLED = True
|
||||
except:
|
||||
RUAMELYAML_INSTALLED = False
|
||||
|
||||
from jc.cli import JcCli
|
||||
import jc.parsers.url as url_parser
|
||||
import jc.parsers.proc as proc_parser
|
||||
@ -47,164 +55,165 @@ class MyTests(unittest.TestCase):
|
||||
resulting_attributes = (cli.magic_found_parser, cli.magic_options, cli.magic_run_command)
|
||||
self.assertEqual(expected, resulting_attributes)
|
||||
|
||||
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||
def test_cli_set_env_colors(self):
|
||||
if PYGMENTS_INSTALLED:
|
||||
if pygments.__version__.startswith('2.3.'):
|
||||
env = {
|
||||
'': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
' ': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
'default,default,default,default': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
'red,red,red,red': {
|
||||
Name.Tag: 'bold #ansidarkred',
|
||||
Keyword: '#ansidarkred',
|
||||
Number: '#ansidarkred',
|
||||
String: '#ansidarkred'
|
||||
},
|
||||
'red,red,yada,red': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
'red,red,red': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
'red,red,red,red,red,red': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
}
|
||||
if pygments.__version__.startswith('2.3.'):
|
||||
env = {
|
||||
'': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
' ': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
'default,default,default,default': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
'red,red,red,red': {
|
||||
Name.Tag: 'bold #ansidarkred',
|
||||
Keyword: '#ansidarkred',
|
||||
Number: '#ansidarkred',
|
||||
String: '#ansidarkred'
|
||||
},
|
||||
'red,red,yada,red': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
'red,red,red': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
},
|
||||
'red,red,red,red,red,red': {
|
||||
Name.Tag: 'bold #ansidarkblue',
|
||||
Keyword: '#ansidarkgray',
|
||||
Number: '#ansipurple',
|
||||
String: '#ansidarkgreen'
|
||||
}
|
||||
else:
|
||||
env = {
|
||||
'': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
' ': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
'default,default,default,default': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
'red,red,red,red': {
|
||||
Name.Tag: 'bold ansired',
|
||||
Keyword: 'ansired',
|
||||
Number: 'ansired',
|
||||
String: 'ansired'
|
||||
},
|
||||
'red,red,yada,red': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
'red,red,red': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
'red,red,red,red,red,red': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
}
|
||||
}
|
||||
else:
|
||||
env = {
|
||||
'': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
' ': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
'default,default,default,default': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
'red,red,red,red': {
|
||||
Name.Tag: 'bold ansired',
|
||||
Keyword: 'ansired',
|
||||
Number: 'ansired',
|
||||
String: 'ansired'
|
||||
},
|
||||
'red,red,yada,red': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
'red,red,red': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
},
|
||||
'red,red,red,red,red,red': {
|
||||
Name.Tag: 'bold ansiblue',
|
||||
Keyword: 'ansibrightblack',
|
||||
Number: 'ansimagenta',
|
||||
String: 'ansigreen'
|
||||
}
|
||||
}
|
||||
|
||||
for jc_colors, expected_colors in env.items():
|
||||
cli = JcCli()
|
||||
os.environ["JC_COLORS"] = jc_colors
|
||||
cli.set_custom_colors()
|
||||
self.assertEqual(cli.custom_colors, expected_colors)
|
||||
for jc_colors, expected_colors in env.items():
|
||||
cli = JcCli()
|
||||
os.environ["JC_COLORS"] = jc_colors
|
||||
cli.set_custom_colors()
|
||||
self.assertEqual(cli.custom_colors, expected_colors)
|
||||
|
||||
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||
def test_cli_json_out(self):
|
||||
if PYGMENTS_INSTALLED:
|
||||
test_input = [
|
||||
None,
|
||||
{},
|
||||
[],
|
||||
'',
|
||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||
]
|
||||
|
||||
if pygments.__version__.startswith('2.3.'):
|
||||
expected_output = [
|
||||
'\x1b[30;01mnull\x1b[39;00m',
|
||||
'{}',
|
||||
'[]',
|
||||
'\x1b[32m""\x1b[39m',
|
||||
'{\x1b[34;01m"key1"\x1b[39;00m:\x1b[32m"value1"\x1b[39m,\x1b[34;01m"key2"\x1b[39;00m:\x1b[35m2\x1b[39m,\x1b[34;01m"key3"\x1b[39;00m:\x1b[30;01mnull\x1b[39;00m,\x1b[34;01m"key4"\x1b[39;00m:\x1b[35m3.14\x1b[39m,\x1b[34;01m"key5"\x1b[39;00m:\x1b[30;01mtrue\x1b[39;00m}'
|
||||
]
|
||||
else:
|
||||
expected_output = [
|
||||
'\x1b[90mnull\x1b[39m',
|
||||
'{}',
|
||||
'[]',
|
||||
'\x1b[32m""\x1b[39m',
|
||||
'{\x1b[34;01m"key1"\x1b[39;00m:\x1b[32m"value1"\x1b[39m,\x1b[34;01m"key2"\x1b[39;00m:\x1b[35m2\x1b[39m,\x1b[34;01m"key3"\x1b[39;00m:\x1b[90mnull\x1b[39m,\x1b[34;01m"key4"\x1b[39;00m:\x1b[35m3.14\x1b[39m,\x1b[34;01m"key5"\x1b[39;00m:\x1b[90mtrue\x1b[39m}'
|
||||
]
|
||||
|
||||
for test_dict, expected_json in zip(test_input, expected_output):
|
||||
cli = JcCli()
|
||||
os.environ["JC_COLORS"] = "default,default,default,default"
|
||||
cli.set_custom_colors()
|
||||
cli.data_out = test_dict
|
||||
self.assertEqual(cli.json_out(), expected_json)
|
||||
|
||||
def test_cli_json_out_mono(self):
|
||||
if PYGMENTS_INSTALLED:
|
||||
test_input = [
|
||||
None,
|
||||
{},
|
||||
[],
|
||||
'',
|
||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||
]
|
||||
test_input = [
|
||||
None,
|
||||
{},
|
||||
[],
|
||||
'',
|
||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||
]
|
||||
|
||||
if pygments.__version__.startswith('2.3.'):
|
||||
expected_output = [
|
||||
'null',
|
||||
'\x1b[30;01mnull\x1b[39;00m',
|
||||
'{}',
|
||||
'[]',
|
||||
'""',
|
||||
'{"key1":"value1","key2":2,"key3":null,"key4":3.14,"key5":true}'
|
||||
'\x1b[32m""\x1b[39m',
|
||||
'{\x1b[34;01m"key1"\x1b[39;00m:\x1b[32m"value1"\x1b[39m,\x1b[34;01m"key2"\x1b[39;00m:\x1b[35m2\x1b[39m,\x1b[34;01m"key3"\x1b[39;00m:\x1b[30;01mnull\x1b[39;00m,\x1b[34;01m"key4"\x1b[39;00m:\x1b[35m3.14\x1b[39m,\x1b[34;01m"key5"\x1b[39;00m:\x1b[30;01mtrue\x1b[39;00m}'
|
||||
]
|
||||
else:
|
||||
expected_output = [
|
||||
'\x1b[90mnull\x1b[39m',
|
||||
'{}',
|
||||
'[]',
|
||||
'\x1b[32m""\x1b[39m',
|
||||
'{\x1b[34;01m"key1"\x1b[39;00m:\x1b[32m"value1"\x1b[39m,\x1b[34;01m"key2"\x1b[39;00m:\x1b[35m2\x1b[39m,\x1b[34;01m"key3"\x1b[39;00m:\x1b[90mnull\x1b[39m,\x1b[34;01m"key4"\x1b[39;00m:\x1b[35m3.14\x1b[39m,\x1b[34;01m"key5"\x1b[39;00m:\x1b[90mtrue\x1b[39m}'
|
||||
]
|
||||
|
||||
for test_dict, expected_json in zip(test_input, expected_output):
|
||||
cli = JcCli()
|
||||
cli.set_custom_colors()
|
||||
cli.mono = True
|
||||
cli.data_out = test_dict
|
||||
self.assertEqual(cli.json_out(), expected_json)
|
||||
for test_dict, expected_json in zip(test_input, expected_output):
|
||||
cli = JcCli()
|
||||
os.environ["JC_COLORS"] = "default,default,default,default"
|
||||
cli.set_custom_colors()
|
||||
cli.data_out = test_dict
|
||||
self.assertEqual(cli.json_out(), expected_json)
|
||||
|
||||
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||
def test_cli_json_out_mono(self):
|
||||
test_input = [
|
||||
None,
|
||||
{},
|
||||
[],
|
||||
'',
|
||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||
]
|
||||
|
||||
expected_output = [
|
||||
'null',
|
||||
'{}',
|
||||
'[]',
|
||||
'""',
|
||||
'{"key1":"value1","key2":2,"key3":null,"key4":3.14,"key5":true}'
|
||||
]
|
||||
|
||||
for test_dict, expected_json in zip(test_input, expected_output):
|
||||
cli = JcCli()
|
||||
cli.set_custom_colors()
|
||||
cli.mono = True
|
||||
cli.data_out = test_dict
|
||||
self.assertEqual(cli.json_out(), expected_json)
|
||||
|
||||
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||
def test_cli_json_out_pretty(self):
|
||||
test_input = [
|
||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||
@ -229,40 +238,60 @@ class MyTests(unittest.TestCase):
|
||||
cli.data_out = test_dict
|
||||
self.assertEqual(cli.json_out(), expected_json)
|
||||
|
||||
@unittest.skipIf(PYGMENTS_INSTALLED, 'pygments library installed')
|
||||
def test_cli_json_out_pretty_no_pygments(self):
|
||||
test_input = [
|
||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||
{"key1": [{"subkey1": "subvalue1"}, {"subkey2": [1, 2, 3]}], "key2": True}
|
||||
]
|
||||
|
||||
expected_output = [
|
||||
'{\n "key1": "value1",\n "key2": 2,\n "key3": null,\n "key4": 3.14,\n "key5": true\n}',
|
||||
'{\n "key1": [\n {\n "subkey1": "subvalue1"\n },\n {\n "subkey2": [\n 1,\n 2,\n 3\n ]\n }\n ],\n "key2": true\n}'
|
||||
]
|
||||
|
||||
for test_dict, expected_json in zip(test_input, expected_output):
|
||||
cli = JcCli()
|
||||
cli.pretty = True
|
||||
cli.set_custom_colors()
|
||||
cli.data_out = test_dict
|
||||
self.assertEqual(cli.json_out(), expected_json)
|
||||
|
||||
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||
def test_cli_yaml_out(self):
|
||||
if PYGMENTS_INSTALLED:
|
||||
test_input = [
|
||||
None,
|
||||
{},
|
||||
[],
|
||||
'',
|
||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||
test_input = [
|
||||
None,
|
||||
{},
|
||||
[],
|
||||
'',
|
||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||
]
|
||||
|
||||
if pygments.__version__.startswith('2.3.'):
|
||||
expected_output = [
|
||||
'---\n...',
|
||||
'--- {}',
|
||||
'--- []',
|
||||
"--- \x1b[32m'\x1b[39m\x1b[32m'\x1b[39m",
|
||||
'---\nkey1: value1\nkey2: 2\nkey3:\nkey4: 3.14\nkey5: true'
|
||||
]
|
||||
else:
|
||||
expected_output = [
|
||||
'---\n...',
|
||||
'--- {}',
|
||||
'--- []',
|
||||
"--- \x1b[32m'\x1b[39m\x1b[32m'\x1b[39m",
|
||||
'---\n\x1b[34;01mkey1\x1b[39;00m: value1\n\x1b[34;01mkey2\x1b[39;00m: 2\n\x1b[34;01mkey3\x1b[39;00m:\n\x1b[34;01mkey4\x1b[39;00m: 3.14\n\x1b[34;01mkey5\x1b[39;00m: true'
|
||||
]
|
||||
|
||||
if pygments.__version__.startswith('2.3.'):
|
||||
expected_output = [
|
||||
'---\n...',
|
||||
'--- {}',
|
||||
'--- []',
|
||||
"--- \x1b[32m'\x1b[39m\x1b[32m'\x1b[39m",
|
||||
'---\nkey1: value1\nkey2: 2\nkey3:\nkey4: 3.14\nkey5: true'
|
||||
]
|
||||
else:
|
||||
expected_output = [
|
||||
'---\n...',
|
||||
'--- {}',
|
||||
'--- []',
|
||||
"--- \x1b[32m'\x1b[39m\x1b[32m'\x1b[39m",
|
||||
'---\n\x1b[34;01mkey1\x1b[39;00m: value1\n\x1b[34;01mkey2\x1b[39;00m: 2\n\x1b[34;01mkey3\x1b[39;00m:\n\x1b[34;01mkey4\x1b[39;00m: 3.14\n\x1b[34;01mkey5\x1b[39;00m: true'
|
||||
]
|
||||
|
||||
for test_dict, expected_json in zip(test_input, expected_output):
|
||||
cli = JcCli()
|
||||
os.environ["JC_COLORS"] = "default,default,default,default"
|
||||
cli.set_custom_colors()
|
||||
cli.data_out = test_dict
|
||||
self.assertEqual(cli.yaml_out(), expected_json)
|
||||
for test_dict, expected_json in zip(test_input, expected_output):
|
||||
cli = JcCli()
|
||||
os.environ["JC_COLORS"] = "default,default,default,default"
|
||||
cli.set_custom_colors()
|
||||
cli.data_out = test_dict
|
||||
self.assertEqual(cli.yaml_out(), expected_json)
|
||||
|
||||
@unittest.skipIf(not RUAMELYAML_INSTALLED, 'ruamel.yaml library not installed')
|
||||
def test_cli_yaml_out_mono(self):
|
||||
test_input = [
|
||||
None,
|
||||
@ -295,6 +324,10 @@ class MyTests(unittest.TestCase):
|
||||
self.assertGreaterEqual(cli.about_jc()['parser_count'], 55)
|
||||
self.assertEqual(cli.about_jc()['parser_count'], len(cli.about_jc()['parsers']))
|
||||
|
||||
def test_cli_parsers_text(self):
|
||||
cli = JcCli()
|
||||
self.assertIsNot(cli.parsers_text, '')
|
||||
|
||||
def test_add_meta_to_simple_dict(self):
|
||||
cli = JcCli()
|
||||
cli.data_out = {'a': 1, 'b': 2}
|
||||
|
@ -11,6 +11,12 @@ class MyTests(unittest.TestCase):
|
||||
p = jc.lib.get_parser('arp')
|
||||
self.assertIsInstance(p, ModuleType)
|
||||
|
||||
def test_lib_get_parser_broken_parser(self):
|
||||
"""get_parser substitutes the disabled_parser if a parser is broken"""
|
||||
broken = jc.lib.get_parser('broken_parser')
|
||||
disabled = jc.lib.get_parser('disabled_parser')
|
||||
self.assertIs(broken, disabled)
|
||||
|
||||
def test_lib_get_parser_module(self):
|
||||
p = jc.lib.get_parser(csv_parser)
|
||||
self.assertIsInstance(p, ModuleType)
|
||||
|
@ -65,6 +65,14 @@ class MyTests(unittest.TestCase):
|
||||
"""
|
||||
self.assertEqual(jc.parsers.uptime.parse(self.osx_10_14_6_uptime, quiet=True), self.osx_10_14_6_uptime_json)
|
||||
|
||||
def test_uptime_busybox(self):
|
||||
"""
|
||||
Test 'uptime' on busybox with no user information
|
||||
"""
|
||||
data = '00:03:32 up 3 min, load average: 0.00, 0.00, 0.00'
|
||||
expected = {"time":"00:03:32","uptime":"3 min","load_1m":0.0,"load_5m":0.0,"load_15m":0.0,"time_hour":0,"time_minute":3,"time_second":32,"uptime_days":0,"uptime_hours":0,"uptime_minutes":3,"uptime_total_seconds":180}
|
||||
self.assertEqual(jc.parsers.uptime.parse(data, quiet=True), expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -2,7 +2,6 @@ import os
|
||||
import unittest
|
||||
import json
|
||||
import jc.parsers.xml
|
||||
import xmltodict
|
||||
|
||||
# fix for whether tests are run directly or via runtests.sh
|
||||
try:
|
||||
@ -10,10 +9,18 @@ try:
|
||||
except:
|
||||
from _vendor.packaging import version # type: ignore
|
||||
|
||||
# check the version of installed xmltodict library
|
||||
try:
|
||||
import xmltodict
|
||||
XMLTODICT_INSTALLED = True
|
||||
XMLTODICT_0_13_0_OR_HIGHER = version.parse(xmltodict.__version__) >= version.parse('0.13.0') # type: ignore
|
||||
except:
|
||||
XMLTODICT_INSTALLED = False
|
||||
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
XMLTODICT_0_13_0_OR_HIGHER = version.parse(xmltodict.__version__) >= version.parse('0.13.0')
|
||||
|
||||
|
||||
@unittest.skipIf(not XMLTODICT_INSTALLED, 'xmltodict library not installed')
|
||||
class MyTests(unittest.TestCase):
|
||||
|
||||
# input
|
||||
|
@ -1,36 +1,33 @@
|
||||
import pprint
|
||||
import re
|
||||
import unittest
|
||||
from typing import Optional
|
||||
|
||||
from jc.parsers.xrandr import (
|
||||
_parse_screen,
|
||||
_parse_device,
|
||||
_parse_mode,
|
||||
_parse_model,
|
||||
_device_pattern,
|
||||
_screen_pattern,
|
||||
_mode_pattern,
|
||||
_frequencies_pattern,
|
||||
_edid_head_pattern,
|
||||
_edid_line_pattern,
|
||||
parse,
|
||||
Mode,
|
||||
Model,
|
||||
Device,
|
||||
Screen
|
||||
Edid,
|
||||
_Line,
|
||||
LineType,
|
||||
ResolutionMode,
|
||||
Response,
|
||||
Screen,
|
||||
_device_pattern,
|
||||
_frequencies_pattern,
|
||||
_parse_device,
|
||||
_parse_resolution_mode,
|
||||
_parse_screen,
|
||||
_resolution_mode_pattern,
|
||||
_screen_pattern,
|
||||
parse,
|
||||
)
|
||||
import jc.parsers.xrandr
|
||||
|
||||
|
||||
class XrandrTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
jc.parsers.xrandr.parse_state = {}
|
||||
|
||||
def test_xrandr_nodata(self):
|
||||
"""
|
||||
Test 'xrandr' with no data
|
||||
"""
|
||||
self.assertEqual(parse("", quiet=True), {})
|
||||
self.assertEqual(parse("", quiet=True), {"screens": []})
|
||||
|
||||
def test_regexes(self):
|
||||
devices = [
|
||||
@ -61,37 +58,30 @@ class XrandrTests(unittest.TestCase):
|
||||
"1400x900 59.96 59.88",
|
||||
]
|
||||
for mode in modes:
|
||||
match = re.match(_mode_pattern, mode)
|
||||
match = re.match(_resolution_mode_pattern, mode)
|
||||
self.assertIsNotNone(match)
|
||||
if match:
|
||||
rest = match.groupdict()["rest"]
|
||||
self.assertIsNotNone(re.match(_frequencies_pattern, rest))
|
||||
|
||||
edid_lines = [
|
||||
" EDID: ",
|
||||
" 00ffffffffffff000469d41901010101 ",
|
||||
" 2011010308291a78ea8585a6574a9c26 ",
|
||||
" 125054bfef80714f8100810f81408180 ",
|
||||
" 9500950f01019a29a0d0518422305098 ",
|
||||
" 360098ff1000001c000000fd00374b1e ",
|
||||
" 530f000a202020202020000000fc0041 ",
|
||||
" 535553205657313933530a20000000ff ",
|
||||
" 0037384c383032313130370a20200077 ",
|
||||
]
|
||||
def test_line_categorize(self):
|
||||
base = "eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm"
|
||||
resolution_mode = " 320x240 60.05"
|
||||
prop_key = " EDID:"
|
||||
prop_value = " 00ffffffffffff0006af3d5700000000"
|
||||
invalid = ""
|
||||
|
||||
for i in range(len(edid_lines)):
|
||||
line = edid_lines[i]
|
||||
if i == 0:
|
||||
match = re.match(_edid_head_pattern, line)
|
||||
else:
|
||||
match = re.match(_edid_line_pattern, line)
|
||||
|
||||
self.assertIsNotNone(match)
|
||||
self.assertEqual(LineType.Device, _Line.categorize(base).t)
|
||||
self.assertEqual(LineType.ResolutionMode, _Line.categorize(resolution_mode).t)
|
||||
self.assertEqual(LineType.PropKey, _Line.categorize(prop_key).t)
|
||||
self.assertEqual(LineType.PropValue, _Line.categorize(prop_value).t)
|
||||
with self.assertRaises(Exception):
|
||||
_Line.categorize(invalid)
|
||||
|
||||
def test_screens(self):
|
||||
sample = "Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767"
|
||||
|
||||
actual: Optional[Screen] = _parse_screen([sample])
|
||||
line = _Line.categorize(sample)
|
||||
actual: Optional[Screen] = _parse_screen(line)
|
||||
self.assertIsNotNone(actual)
|
||||
|
||||
expected = {
|
||||
@ -110,7 +100,8 @@ class XrandrTests(unittest.TestCase):
|
||||
sample = (
|
||||
"Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384"
|
||||
)
|
||||
actual = _parse_screen([sample])
|
||||
line = _Line.categorize(sample)
|
||||
actual = _parse_screen(line)
|
||||
if actual:
|
||||
self.assertEqual(320, actual["minimum_width"])
|
||||
else:
|
||||
@ -119,7 +110,8 @@ class XrandrTests(unittest.TestCase):
|
||||
def test_device(self):
|
||||
# regex101 sample link for tests/edits https://regex101.com/r/3cHMv3/1
|
||||
sample = "eDP1 connected primary 1920x1080+0+0 left (normal left inverted right x axis y axis) 310mm x 170mm"
|
||||
actual: Optional[Device] = _parse_device([sample])
|
||||
line = _Line.categorize(sample)
|
||||
actual: Optional[Device] = _parse_device(line)
|
||||
|
||||
expected = {
|
||||
"device_name": "eDP1",
|
||||
@ -140,17 +132,19 @@ class XrandrTests(unittest.TestCase):
|
||||
for k, v in expected.items():
|
||||
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
||||
|
||||
with open("tests/fixtures/generic/xrandr_device.out", "r") as f:
|
||||
extended_sample = f.read().splitlines()
|
||||
extended_sample.reverse()
|
||||
# with open("tests/fixtures/generic/xrandr_device.out", "r") as f:
|
||||
# extended_sample = f.read().splitlines()
|
||||
|
||||
device = _parse_device(extended_sample)
|
||||
if device:
|
||||
self.assertEqual(59.94, device["modes"][12]["frequencies"][4]["frequency"])
|
||||
# device = _parse_device(extended_sample)
|
||||
# if device:
|
||||
# self.assertEqual(
|
||||
# 59.94, device["resolution_modes"][12]["frequencies"][4]["frequency"]
|
||||
# )
|
||||
|
||||
def test_device_with_reflect(self):
|
||||
sample = "VGA-1 connected primary 1920x1080+0+0 left X and Y axis (normal left inverted right x axis y axis) 310mm x 170mm"
|
||||
actual: Optional[Device] = _parse_device([sample])
|
||||
line = _Line.categorize(sample)
|
||||
actual: Optional[Device] = _parse_device(line)
|
||||
|
||||
expected = {
|
||||
"device_name": "VGA-1",
|
||||
@ -173,7 +167,7 @@ class XrandrTests(unittest.TestCase):
|
||||
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
||||
|
||||
def test_mode(self):
|
||||
sample_1 = "1920x1080 60.03*+ 59.93"
|
||||
sample_1 = " 1920x1080 60.03*+ 59.93"
|
||||
expected = {
|
||||
"frequencies": [
|
||||
{"frequency": 60.03, "is_current": True, "is_preferred": True},
|
||||
@ -183,7 +177,8 @@ class XrandrTests(unittest.TestCase):
|
||||
"resolution_height": 1080,
|
||||
"is_high_resolution": False,
|
||||
}
|
||||
actual: Optional[Mode] = _parse_mode(sample_1)
|
||||
line = _Line.categorize(sample_1)
|
||||
actual: Optional[ResolutionMode] = _parse_resolution_mode(line)
|
||||
|
||||
self.assertIsNotNone(actual)
|
||||
|
||||
@ -191,8 +186,9 @@ class XrandrTests(unittest.TestCase):
|
||||
for k, v in expected.items():
|
||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
|
||||
sample_2 = " 1920x1080i 60.00 50.00 59.94"
|
||||
actual: Optional[Mode] = _parse_mode(sample_2)
|
||||
sample_2 = " 1920x1080i 60.00 50.00 59.94"
|
||||
line = _Line.categorize(sample_2)
|
||||
actual: Optional[ResolutionMode] = _parse_resolution_mode(line)
|
||||
self.assertIsNotNone(actual)
|
||||
if actual:
|
||||
self.assertEqual(True, actual["is_high_resolution"])
|
||||
@ -205,7 +201,9 @@ class XrandrTests(unittest.TestCase):
|
||||
actual = parse(txt, quiet=True)
|
||||
|
||||
self.assertEqual(1, len(actual["screens"]))
|
||||
self.assertEqual(18, len(actual["screens"][0]["devices"][0]["modes"]))
|
||||
self.assertEqual(
|
||||
18, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
||||
)
|
||||
|
||||
def test_complete_2(self):
|
||||
with open("tests/fixtures/generic/xrandr_2.out", "r") as f:
|
||||
@ -213,7 +211,9 @@ class XrandrTests(unittest.TestCase):
|
||||
actual = parse(txt, quiet=True)
|
||||
|
||||
self.assertEqual(1, len(actual["screens"]))
|
||||
self.assertEqual(38, len(actual["screens"][0]["devices"][0]["modes"]))
|
||||
self.assertEqual(
|
||||
38, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
||||
)
|
||||
|
||||
def test_complete_3(self):
|
||||
with open("tests/fixtures/generic/xrandr_3.out", "r") as f:
|
||||
@ -232,84 +232,119 @@ class XrandrTests(unittest.TestCase):
|
||||
actual = parse(txt, quiet=True)
|
||||
|
||||
self.assertEqual(1, len(actual["screens"]))
|
||||
self.assertEqual(2, len(actual["screens"][0]["devices"][0]["modes"]))
|
||||
self.assertEqual(2, len(actual["screens"][0]["devices"][0]["resolution_modes"]))
|
||||
|
||||
def test_complete_5(self):
|
||||
with open("tests/fixtures/generic/xrandr_properties.out", "r") as f:
|
||||
with open("tests/fixtures/generic/xrandr_properties_1.out", "r") as f:
|
||||
txt = f.read()
|
||||
actual = parse(txt, quiet=True)
|
||||
|
||||
self.assertEqual(1, len(actual["screens"]))
|
||||
self.assertEqual(29, len(actual["screens"][0]["devices"][0]["modes"]))
|
||||
self.assertEqual(
|
||||
38, len(actual["screens"][0]["devices"][0]["resolution_modes"])
|
||||
)
|
||||
|
||||
def test_model(self):
|
||||
asus_edid = [
|
||||
" EDID: ",
|
||||
" 00ffffffffffff000469d41901010101",
|
||||
" 2011010308291a78ea8585a6574a9c26",
|
||||
" 125054bfef80714f8100810f81408180",
|
||||
" 9500950f01019a29a0d0518422305098",
|
||||
" 360098ff1000001c000000fd00374b1e",
|
||||
" 530f000a202020202020000000fc0041",
|
||||
" 535553205657313933530a20000000ff",
|
||||
" 0037384c383032313130370a20200077",
|
||||
]
|
||||
asus_edid.reverse()
|
||||
# def test_model(self):
|
||||
# asus_edid = [
|
||||
# " EDID: ",
|
||||
# " 00ffffffffffff000469d41901010101",
|
||||
# " 2011010308291a78ea8585a6574a9c26",
|
||||
# " 125054bfef80714f8100810f81408180",
|
||||
# " 9500950f01019a29a0d0518422305098",
|
||||
# " 360098ff1000001c000000fd00374b1e",
|
||||
# " 530f000a202020202020000000fc0041",
|
||||
# " 535553205657313933530a20000000ff",
|
||||
# " 0037384c383032313130370a20200077",
|
||||
# ]
|
||||
# asus_edid.reverse()
|
||||
|
||||
expected = {
|
||||
"name": "ASUS VW193S",
|
||||
"product_id": "6612",
|
||||
"serial_number": "78L8021107",
|
||||
}
|
||||
# expected = {
|
||||
# "name": "ASUS VW193S",
|
||||
# "product_id": "6612",
|
||||
# "serial_number": "78L8021107",
|
||||
# }
|
||||
|
||||
actual: Optional[Model] = _parse_model(asus_edid)
|
||||
self.assertIsNotNone(actual)
|
||||
# actual: Optional[EdidModel] = _parse_model(asus_edid)
|
||||
# self.assertIsNotNone(actual)
|
||||
|
||||
if actual:
|
||||
for k, v in expected.items():
|
||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
# if actual:
|
||||
# for k, v in expected.items():
|
||||
# self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
|
||||
generic_edid = [
|
||||
" EDID: ",
|
||||
" 00ffffffffffff004ca3523100000000",
|
||||
" 0014010380221378eac8959e57549226",
|
||||
" 0f505400000001010101010101010101",
|
||||
" 010101010101381d56d4500016303020",
|
||||
" 250058c2100000190000000f00000000",
|
||||
" 000000000025d9066a00000000fe0053",
|
||||
" 414d53554e470a204ca34154000000fe",
|
||||
" 004c544e313536415432343430310018",
|
||||
]
|
||||
generic_edid.reverse()
|
||||
# generic_edid = [
|
||||
# " EDID: ",
|
||||
# " 00ffffffffffff004ca3523100000000",
|
||||
# " 0014010380221378eac8959e57549226",
|
||||
# " 0f505400000001010101010101010101",
|
||||
# " 010101010101381d56d4500016303020",
|
||||
# " 250058c2100000190000000f00000000",
|
||||
# " 000000000025d9066a00000000fe0053",
|
||||
# " 414d53554e470a204ca34154000000fe",
|
||||
# " 004c544e313536415432343430310018",
|
||||
# ]
|
||||
# generic_edid.reverse()
|
||||
|
||||
expected = {
|
||||
"name": "Generic",
|
||||
"product_id": "12626",
|
||||
"serial_number": "0",
|
||||
}
|
||||
# expected = {
|
||||
# "name": "Generic",
|
||||
# "product_id": "12626",
|
||||
# "serial_number": "0",
|
||||
# }
|
||||
|
||||
jc.parsers.xrandr.parse_state = {}
|
||||
actual: Optional[Model] = _parse_model(generic_edid)
|
||||
self.assertIsNotNone(actual)
|
||||
# jc.parsers.xrandr.parse_state = {}
|
||||
# actual: Optional[EdidModel] = _parse_model(generic_edid)
|
||||
# self.assertIsNotNone(actual)
|
||||
|
||||
if actual:
|
||||
for k, v in expected.items():
|
||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
|
||||
empty_edid = [""]
|
||||
actual: Optional[Model] = _parse_model(empty_edid)
|
||||
self.assertIsNone(actual)
|
||||
# if actual:
|
||||
# for k, v in expected.items():
|
||||
# self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||
|
||||
# empty_edid = [""]
|
||||
# actual: Optional[EdidModel] = _parse_model(empty_edid)
|
||||
# self.assertIsNone(actual)
|
||||
|
||||
def test_issue_490(self):
|
||||
"""test for issue 490: https://github.com/kellyjonbrazil/jc/issues/490"""
|
||||
data_in = '''\
|
||||
data_in = """\
|
||||
Screen 0: minimum 1024 x 600, current 1024 x 600, maximum 1024 x 600
|
||||
default connected 1024x600+0+0 0mm x 0mm
|
||||
1024x600 0.00*
|
||||
'''
|
||||
expected = {"screens":[{"devices":[{"modes":[{"resolution_width":1024,"resolution_height":600,"is_high_resolution":False,"frequencies":[{"frequency":0.0,"is_current":True,"is_preferred":False}]}],"is_connected":True,"is_primary":False,"device_name":"default","rotation":"normal","reflection":"normal","resolution_width":1024,"resolution_height":600,"offset_width":0,"offset_height":0,"dimension_width":0,"dimension_height":0}],"screen_number":0,"minimum_width":1024,"minimum_height":600,"current_width":1024,"current_height":600,"maximum_width":1024,"maximum_height":600}]}
|
||||
self.assertEqual(jc.parsers.xrandr.parse(data_in), expected)
|
||||
"""
|
||||
actual: Response = parse(data_in)
|
||||
self.maxDiff = None
|
||||
self.assertEqual(1024, actual["screens"][0]["devices"][0]["resolution_width"])
|
||||
|
||||
def test_issue_525(self):
|
||||
self.maxDiff = None
|
||||
with open("tests/fixtures/generic/xrandr_issue_525.out", "r") as f:
|
||||
txt = f.read()
|
||||
actual = parse(txt, quiet=True)
|
||||
dp4 = actual["screens"][0]["devices"][0]["props"]["Broadcast RGB"][1] # type: ignore
|
||||
# pprint.pprint(actual)
|
||||
self.assertEqual("supported: Automatic, Full, Limited 16:235", dp4)
|
||||
edp1_expected_keys = {
|
||||
"EDID",
|
||||
"EdidModel",
|
||||
"scaling mode",
|
||||
"Colorspace",
|
||||
"max bpc",
|
||||
"Broadcast RGB",
|
||||
"panel orientation",
|
||||
"link-status",
|
||||
"CTM",
|
||||
"CONNECTOR_ID",
|
||||
"non-desktop",
|
||||
}
|
||||
actual_keys = set(actual["screens"][0]["devices"][0]["props"].keys())
|
||||
self.assertSetEqual(edp1_expected_keys, actual_keys)
|
||||
expected_edid_model = {
|
||||
"name": "Generic",
|
||||
"product_id": "22333",
|
||||
"serial_number": "0",
|
||||
}
|
||||
self.assertDictEqual(
|
||||
expected_edid_model,
|
||||
actual["screens"][0]["devices"][0]["props"]["EdidModel"], # type: ignore
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -5,7 +5,14 @@ import jc.parsers.yaml
|
||||
|
||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
try:
|
||||
import ruamel.yaml
|
||||
RUAMELYAML_INSTALLED = True
|
||||
except:
|
||||
RUAMELYAML_INSTALLED = False
|
||||
|
||||
|
||||
@unittest.skipIf(not RUAMELYAML_INSTALLED, 'ruamel.yaml library not installed')
|
||||
class MyTests(unittest.TestCase):
|
||||
|
||||
# input
|
||||
|
Reference in New Issue
Block a user