mirror of
https://github.com/kellyjonbrazil/jc.git
synced 2025-06-19 00:17:51 +02:00
@ -1,5 +1,14 @@
|
|||||||
jc changelog
|
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
|
20240204 v1.25.0
|
||||||
- Add `--slurp` functionality to wrap output from multiple lines into a single array.
|
- 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.)
|
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)
|
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)
|
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:
|
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:
|
For example:
|
||||||
|
|
||||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_meminfo
|
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_meminfo
|
||||||
|
|
||||||
Examples:
|
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.
|
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)
|
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,
|
"maximum_height": integer,
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"modes": [
|
"resolution_modes": [
|
||||||
{
|
{
|
||||||
"resolution_width": integer,
|
"resolution_width": integer,
|
||||||
"resolution_height": integer,
|
"resolution_height": integer,
|
||||||
@ -82,7 +82,7 @@ Examples:
|
|||||||
"maximum_height": 32767,
|
"maximum_height": 32767,
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"modes": [
|
"resolution_modes": [
|
||||||
{
|
{
|
||||||
"resolution_width": 1920,
|
"resolution_width": 1920,
|
||||||
"resolution_height": 1080,
|
"resolution_height": 1080,
|
||||||
@ -143,7 +143,7 @@ Examples:
|
|||||||
"maximum_height": 32767,
|
"maximum_height": 32767,
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"modes": [
|
"resolution_modes": [
|
||||||
{
|
{
|
||||||
"resolution_width": 1920,
|
"resolution_width": 1920,
|
||||||
"resolution_height": 1080,
|
"resolution_height": 1080,
|
||||||
@ -199,7 +199,7 @@ Examples:
|
|||||||
### parse
|
### parse
|
||||||
|
|
||||||
```python
|
```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
|
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)
|
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
|
ensure_ascii=self.ascii_only
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self.mono:
|
if not self.mono and PYGMENTS_INSTALLED:
|
||||||
class JcStyle(Style):
|
class JcStyle(Style):
|
||||||
styles: CustomColorType = self.custom_colors
|
styles: CustomColorType = self.custom_colors
|
||||||
|
|
||||||
@ -540,13 +540,12 @@ class JcCli():
|
|||||||
if self.magic_run_command_str.startswith('/proc'):
|
if self.magic_run_command_str.startswith('/proc'):
|
||||||
try:
|
try:
|
||||||
self.magic_found_parser = 'proc'
|
self.magic_found_parser = 'proc'
|
||||||
|
filelist = shlex.split(self.magic_run_command_str)
|
||||||
|
|
||||||
# multiple proc files detected
|
# multiple proc files detected
|
||||||
if ' ' in self.magic_run_command_str:
|
if len(filelist) > 1:
|
||||||
self.slurp = True
|
self.slurp = True
|
||||||
multi_out: List[str] = []
|
multi_out: List[str] = []
|
||||||
filelist = self.magic_run_command_str.split()
|
|
||||||
filelist = [x.strip() for x in filelist]
|
|
||||||
self.inputlist = filelist
|
self.inputlist = filelist
|
||||||
|
|
||||||
for file in self.inputlist:
|
for file in self.inputlist:
|
||||||
@ -557,7 +556,7 @@ class JcCli():
|
|||||||
|
|
||||||
# single proc file
|
# single proc file
|
||||||
else:
|
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('/Users/kelly/temp' + file)
|
||||||
self.magic_stdout = self.open_text_file(file)
|
self.magic_stdout = self.open_text_file(file)
|
||||||
|
|
||||||
@ -861,6 +860,9 @@ class JcCli():
|
|||||||
self.set_mono()
|
self.set_mono()
|
||||||
self.set_custom_colors()
|
self.set_custom_colors()
|
||||||
|
|
||||||
|
if self.quiet:
|
||||||
|
utils.CLI_QUIET = True
|
||||||
|
|
||||||
if self.verbose_debug:
|
if self.verbose_debug:
|
||||||
tracebackplus.enable(context=11) # type: ignore
|
tracebackplus.enable(context=11) # type: ignore
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ Examples:
|
|||||||
$ jc --pretty /proc/meminfo
|
$ jc --pretty /proc/meminfo
|
||||||
|
|
||||||
Line Slicing:
|
Line Slicing:
|
||||||
$ $ cat output.txt | jc 4:15 --parser # Parse from line 4 to 14
|
$ cat output.txt | jc 4:15 --parser # Parse from line 4 to 14
|
||||||
with parser (zero-based)
|
with parser (zero-based)
|
||||||
|
|
||||||
Parser Documentation:
|
Parser Documentation:
|
||||||
|
16
jc/lib.py
16
jc/lib.py
@ -10,7 +10,7 @@ from jc import appdirs
|
|||||||
from jc import utils
|
from jc import utils
|
||||||
|
|
||||||
|
|
||||||
__version__ = '1.25.0'
|
__version__ = '1.25.1'
|
||||||
|
|
||||||
parsers: List[str] = [
|
parsers: List[str] = [
|
||||||
'acpi',
|
'acpi',
|
||||||
@ -251,7 +251,8 @@ def _is_valid_parser_plugin(name: str, local_parsers_dir: str) -> bool:
|
|||||||
else:
|
else:
|
||||||
utils.warning_message([f'Not installing invalid parser plugin "{parser_mod_name}" at {local_parsers_dir}'])
|
utils.warning_message([f'Not installing invalid parser plugin "{parser_mod_name}" at {local_parsers_dir}'])
|
||||||
return False
|
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
|
||||||
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_mod_name = _cliname_to_modname(parser_mod_name)
|
||||||
parser_cli_name = _modname_to_cliname(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.'
|
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:
|
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():
|
class info():
|
||||||
"""Provides parser metadata (version, author, etc.)"""
|
"""Provides parser metadata (version, author, etc.)"""
|
||||||
version = '2.1'
|
version = '2.2'
|
||||||
description = 'INI file parser'
|
description = 'INI file parser'
|
||||||
author = 'Kelly Brazil'
|
author = 'Kelly Brazil'
|
||||||
author_email = 'kellyjonbrazil@gmail.com'
|
author_email = 'kellyjonbrazil@gmail.com'
|
||||||
@ -87,14 +87,10 @@ class info():
|
|||||||
__version__ = info.version
|
__version__ = info.version
|
||||||
|
|
||||||
|
|
||||||
class MyDict(dict):
|
def _none_to_empty_string(data):
|
||||||
def __setitem__(self, key, value):
|
if data is None:
|
||||||
# convert None values to empty string
|
return ''
|
||||||
if value is None:
|
return data
|
||||||
self[key] = ''
|
|
||||||
|
|
||||||
else:
|
|
||||||
super().__setitem__(key, value)
|
|
||||||
|
|
||||||
|
|
||||||
def _process(proc_data):
|
def _process(proc_data):
|
||||||
@ -110,13 +106,18 @@ def _process(proc_data):
|
|||||||
Dictionary representing the INI file.
|
Dictionary representing the INI file.
|
||||||
"""
|
"""
|
||||||
# remove quotation marks from beginning and end of values
|
# remove quotation marks from beginning and end of values
|
||||||
|
# and convert None to empty string
|
||||||
for k, v in proc_data.items():
|
for k, v in proc_data.items():
|
||||||
if isinstance(v, dict):
|
if isinstance(v, dict):
|
||||||
for key, value in v.items():
|
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
|
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
|
return proc_data
|
||||||
|
|
||||||
@ -143,7 +144,6 @@ def parse(data, raw=False, quiet=False):
|
|||||||
if jc.utils.has_data(data):
|
if jc.utils.has_data(data):
|
||||||
|
|
||||||
ini_parser = configparser.ConfigParser(
|
ini_parser = configparser.ConfigParser(
|
||||||
dict_type = MyDict,
|
|
||||||
allow_no_value=True,
|
allow_no_value=True,
|
||||||
interpolation=None,
|
interpolation=None,
|
||||||
default_section=None,
|
default_section=None,
|
||||||
@ -175,4 +175,3 @@ def parse(data, raw=False, quiet=False):
|
|||||||
raw_output.update(temp_dict)
|
raw_output.update(temp_dict)
|
||||||
|
|
||||||
return raw_output if raw else _process(raw_output)
|
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
|
from typing import Dict, Union
|
||||||
import plistlib
|
import plistlib
|
||||||
import binascii
|
import binascii
|
||||||
@ -53,7 +59,7 @@ import jc.utils
|
|||||||
|
|
||||||
class info():
|
class info():
|
||||||
"""Provides parser metadata (version, author, etc.)"""
|
"""Provides parser metadata (version, author, etc.)"""
|
||||||
version = '1.1'
|
version = '1.2'
|
||||||
description = 'PLIST file parser'
|
description = 'PLIST file parser'
|
||||||
author = 'Kelly Brazil'
|
author = 'Kelly Brazil'
|
||||||
author_email = 'kellyjonbrazil@gmail.com'
|
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:
|
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:
|
For example:
|
||||||
|
|
||||||
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_meminfo
|
https://kellyjonbrazil.github.io/jc/docs/parsers/proc_meminfo
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
|
@ -8,6 +8,16 @@ from typing import ByteString
|
|||||||
|
|
||||||
__all__ = ["Edid"]
|
__all__ = ["Edid"]
|
||||||
|
|
||||||
|
# EDID:
|
||||||
|
# 00ffffffffffff004ca3523100000000
|
||||||
|
# 0014010380221378eac8959e57549226
|
||||||
|
# 0f505400000001010101010101010101
|
||||||
|
# 010101010101381d56d4500016303020
|
||||||
|
# 250058c2100000190000000f00000000
|
||||||
|
# 000000000025d9066a00000000fe0053
|
||||||
|
# 414d53554e470a204ca34154000000fe
|
||||||
|
# 004c544e313536415432343430310018
|
||||||
|
|
||||||
|
|
||||||
class Edid:
|
class Edid:
|
||||||
"""Edid class
|
"""Edid class
|
||||||
@ -64,13 +74,15 @@ class Edid:
|
|||||||
|
|
||||||
_ASPECT_RATIOS = {
|
_ASPECT_RATIOS = {
|
||||||
0b00: (16, 10),
|
0b00: (16, 10),
|
||||||
0b01: ( 4, 3),
|
0b01: (4, 3),
|
||||||
0b10: ( 5, 4),
|
0b10: (5, 4),
|
||||||
0b11: (16, 9),
|
0b11: (16, 9),
|
||||||
}
|
}
|
||||||
|
|
||||||
_RawEdid = namedtuple("RawEdid",
|
_RawEdid = namedtuple(
|
||||||
("header",
|
"RawEdid",
|
||||||
|
(
|
||||||
|
"header",
|
||||||
"manu_id",
|
"manu_id",
|
||||||
"prod_id",
|
"prod_id",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
@ -92,7 +104,8 @@ class Edid:
|
|||||||
"timing_3",
|
"timing_3",
|
||||||
"timing_4",
|
"timing_4",
|
||||||
"extension",
|
"extension",
|
||||||
"checksum")
|
"checksum",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, edid: ByteString):
|
def __init__(self, edid: ByteString):
|
||||||
@ -109,18 +122,20 @@ class Edid:
|
|||||||
unpacked = struct.unpack(self._STRUCT_FORMAT, edid)
|
unpacked = struct.unpack(self._STRUCT_FORMAT, edid)
|
||||||
raw_edid = self._RawEdid(*unpacked)
|
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.")
|
raise ValueError("Invalid header.")
|
||||||
|
|
||||||
self.raw = edid
|
self.raw = edid
|
||||||
self.manufacturer_id = raw_edid.manu_id
|
self.manufacturer_id = raw_edid.manu_id
|
||||||
self.product = raw_edid.prod_id
|
self.product = raw_edid.prod_id
|
||||||
self.year = raw_edid.manu_year + 1990
|
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.type = "digital" if (raw_edid.input_type & 0xFF) else "analog"
|
||||||
self.width = float(raw_edid.width)
|
self.width = float(raw_edid.width)
|
||||||
self.height = float(raw_edid.height)
|
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_standby = bool(raw_edid.features & 0xFF)
|
||||||
self.dpms_suspend = bool(raw_edid.features & 0x7F)
|
self.dpms_suspend = bool(raw_edid.features & 0x7F)
|
||||||
self.dpms_activeoff = bool(raw_edid.features & 0x3F)
|
self.dpms_activeoff = bool(raw_edid.features & 0x3F)
|
||||||
@ -132,22 +147,27 @@ class Edid:
|
|||||||
self.resolutions.append(self._TIMINGS[i])
|
self.resolutions.append(self._TIMINGS[i])
|
||||||
|
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
bytes_data = raw_edid.timings_edid[2*i:2*i+2]
|
bytes_data = raw_edid.timings_edid[2 * i : 2 * i + 2]
|
||||||
if bytes_data == b'\x01\x01':
|
if bytes_data == b"\x01\x01":
|
||||||
continue
|
continue
|
||||||
byte1, byte2 = bytes_data
|
byte1, byte2 = bytes_data
|
||||||
x_res = 8*(int(byte1)+31)
|
x_res = 8 * (int(byte1) + 31)
|
||||||
aspect_ratio = self._ASPECT_RATIOS[(byte2>>6) & 0b11]
|
aspect_ratio = self._ASPECT_RATIOS[(byte2 >> 6) & 0b11]
|
||||||
y_res = int(x_res * aspect_ratio[1]/aspect_ratio[0])
|
y_res = int(x_res * aspect_ratio[1] / aspect_ratio[0])
|
||||||
rate = (int(byte2) & 0b00111111) + 60.0
|
rate = (int(byte2) & 0b00111111) + 60.0
|
||||||
self.resolutions.append((x_res, y_res, rate))
|
self.resolutions.append((x_res, y_res, rate))
|
||||||
|
|
||||||
self.name = None
|
self.name = None
|
||||||
self.serial = 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
|
# "other" descriptor
|
||||||
if timing_bytes[0:2] == b'\x00\x00':
|
if timing_bytes[0:2] == b"\x00\x00":
|
||||||
timing_type = timing_bytes[3]
|
timing_type = timing_bytes[3]
|
||||||
if timing_type in (0xFF, 0xFE, 0xFC):
|
if timing_type in (0xFF, 0xFE, 0xFC):
|
||||||
buffer = timing_bytes[5:]
|
buffer = timing_bytes[5:]
|
||||||
|
@ -65,7 +65,7 @@ import jc.utils
|
|||||||
|
|
||||||
class info():
|
class info():
|
||||||
"""Provides parser metadata (version, author, etc.)"""
|
"""Provides parser metadata (version, author, etc.)"""
|
||||||
version = '1.8'
|
version = '1.9'
|
||||||
description = '`uptime` command parser'
|
description = '`uptime` command parser'
|
||||||
author = 'Kelly Brazil'
|
author = 'Kelly Brazil'
|
||||||
author_email = 'kellyjonbrazil@gmail.com'
|
author_email = 'kellyjonbrazil@gmail.com'
|
||||||
@ -160,10 +160,11 @@ def parse(data, raw=False, quiet=False):
|
|||||||
jc.utils.input_type_check(data)
|
jc.utils.input_type_check(data)
|
||||||
|
|
||||||
raw_output = {}
|
raw_output = {}
|
||||||
cleandata = data.splitlines()
|
|
||||||
|
|
||||||
if jc.utils.has_data(data):
|
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['time'] = time
|
||||||
raw_output['uptime'] = ' '.join(uptime).rstrip(',')
|
raw_output['uptime'] = ' '.join(uptime).rstrip(',')
|
||||||
@ -172,7 +173,14 @@ def parse(data, raw=False, quiet=False):
|
|||||||
raw_output['load_5m'] = load_5m.rstrip(',')
|
raw_output['load_5m'] = load_5m.rstrip(',')
|
||||||
raw_output['load_15m'] = load_15m
|
raw_output['load_15m'] = load_15m
|
||||||
|
|
||||||
if raw:
|
|
||||||
return raw_output
|
|
||||||
else:
|
else:
|
||||||
return _process(raw_output)
|
# 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
|
import jc.utils
|
||||||
from jc.exceptions import LibraryNotInstalled
|
from jc.exceptions import LibraryNotInstalled
|
||||||
|
|
||||||
try:
|
|
||||||
import xmltodict
|
|
||||||
except Exception:
|
|
||||||
raise LibraryNotInstalled('The xmltodict library is not installed.')
|
|
||||||
|
|
||||||
|
|
||||||
class info():
|
class info():
|
||||||
"""Provides parser metadata (version, author, etc.)"""
|
"""Provides parser metadata (version, author, etc.)"""
|
||||||
version = '1.9'
|
version = '1.10'
|
||||||
description = 'XML file parser'
|
description = 'XML file parser'
|
||||||
author = 'Kelly Brazil'
|
author = 'Kelly Brazil'
|
||||||
author_email = 'kellyjonbrazil@gmail.com'
|
author_email = 'kellyjonbrazil@gmail.com'
|
||||||
@ -93,7 +88,7 @@ class info():
|
|||||||
__version__ = info.version
|
__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.
|
Final processing to conform to the schema.
|
||||||
|
|
||||||
@ -105,16 +100,19 @@ def _process(proc_data, has_data=False):
|
|||||||
|
|
||||||
Dictionary representing an XML document.
|
Dictionary representing an XML document.
|
||||||
"""
|
"""
|
||||||
|
if not xml_mod:
|
||||||
|
raise LibraryNotInstalled('The xmltodict library is not installed.')
|
||||||
|
|
||||||
proc_output = []
|
proc_output = []
|
||||||
|
|
||||||
if has_data:
|
if has_data:
|
||||||
# standard output with @ prefix for attributes
|
# standard output with @ prefix for attributes
|
||||||
try:
|
try:
|
||||||
proc_output = xmltodict.parse(proc_data,
|
proc_output = xml_mod.parse(proc_data,
|
||||||
dict_constructor=dict,
|
dict_constructor=dict,
|
||||||
process_comments=True)
|
process_comments=True)
|
||||||
except (ValueError, TypeError):
|
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
|
return proc_output
|
||||||
|
|
||||||
@ -133,6 +131,12 @@ def parse(data, raw=False, quiet=False):
|
|||||||
|
|
||||||
Dictionary. Raw or processed structured data.
|
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.compatibility(__name__, info.compatible, quiet)
|
||||||
jc.utils.input_type_check(data)
|
jc.utils.input_type_check(data)
|
||||||
|
|
||||||
@ -156,4 +160,4 @@ def parse(data, raw=False, quiet=False):
|
|||||||
|
|
||||||
return raw_output
|
return raw_output
|
||||||
|
|
||||||
return _process(data, has_data)
|
return _process(data, has_data, xml_mod=xmltodict)
|
||||||
|
@ -28,7 +28,7 @@ Schema:
|
|||||||
"maximum_height": integer,
|
"maximum_height": integer,
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"modes": [
|
"resolution_modes": [
|
||||||
{
|
{
|
||||||
"resolution_width": integer,
|
"resolution_width": integer,
|
||||||
"resolution_height": integer,
|
"resolution_height": integer,
|
||||||
@ -77,7 +77,7 @@ Examples:
|
|||||||
"maximum_height": 32767,
|
"maximum_height": 32767,
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"modes": [
|
"resolution_modes": [
|
||||||
{
|
{
|
||||||
"resolution_width": 1920,
|
"resolution_width": 1920,
|
||||||
"resolution_height": 1080,
|
"resolution_height": 1080,
|
||||||
@ -138,7 +138,7 @@ Examples:
|
|||||||
"maximum_height": 32767,
|
"maximum_height": 32767,
|
||||||
"devices": [
|
"devices": [
|
||||||
{
|
{
|
||||||
"modes": [
|
"resolution_modes": [
|
||||||
{
|
{
|
||||||
"resolution_width": 1920,
|
"resolution_width": 1920,
|
||||||
"resolution_height": 1080,
|
"resolution_height": 1080,
|
||||||
@ -189,16 +189,27 @@ Examples:
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
from collections import defaultdict
|
||||||
|
from enum import Enum
|
||||||
import re
|
import re
|
||||||
from typing import Dict, List, Optional, Union
|
from typing import Dict, List, Tuple, Union
|
||||||
|
|
||||||
import jc.utils
|
import jc.utils
|
||||||
from jc.parsers.pyedid.edid import Edid
|
from jc.parsers.pyedid.edid import Edid
|
||||||
from jc.parsers.pyedid.helpers.edid_helper import EdidHelper
|
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:
|
class info:
|
||||||
"""Provides parser metadata (version, author, etc.)"""
|
"""Provides parser metadata (version, author, etc.)"""
|
||||||
version = "1.4"
|
|
||||||
|
version = "2.0"
|
||||||
description = "`xrandr` command parser"
|
description = "`xrandr` command parser"
|
||||||
author = "Kevin Lyter"
|
author = "Kevin Lyter"
|
||||||
author_email = "code (at) lyterk.com"
|
author_email = "code (at) lyterk.com"
|
||||||
@ -210,36 +221,10 @@ class info:
|
|||||||
|
|
||||||
__version__ = info.version
|
__version__ = info.version
|
||||||
|
|
||||||
# keep parsing state so we know which parsers have already tried the line
|
# NOTE: When developing, comment out the try statement and catch block to get
|
||||||
# Structure is:
|
# TypedDict type hints and valid type errors.
|
||||||
# {
|
|
||||||
# <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
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Added in Python 3.8
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
Frequency = TypedDict(
|
Frequency = TypedDict(
|
||||||
@ -250,8 +235,8 @@ try:
|
|||||||
"is_preferred": bool,
|
"is_preferred": bool,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Mode = TypedDict(
|
ResolutionMode = TypedDict(
|
||||||
"Mode",
|
"ResolutionMode",
|
||||||
{
|
{
|
||||||
"resolution_width": int,
|
"resolution_width": int,
|
||||||
"resolution_height": int,
|
"resolution_height": int,
|
||||||
@ -259,14 +244,15 @@ try:
|
|||||||
"frequencies": List[Frequency],
|
"frequencies": List[Frequency],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Model = TypedDict(
|
EdidModel = TypedDict(
|
||||||
"Model",
|
"EdidModel",
|
||||||
{
|
{
|
||||||
"name": str,
|
"name": str,
|
||||||
"product_id": str,
|
"product_id": str,
|
||||||
"serial_number": str,
|
"serial_number": str,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Props = Dict[str, Union[List[str], EdidModel]]
|
||||||
Device = TypedDict(
|
Device = TypedDict(
|
||||||
"Device",
|
"Device",
|
||||||
{
|
{
|
||||||
@ -282,7 +268,8 @@ try:
|
|||||||
"offset_height": int,
|
"offset_height": int,
|
||||||
"dimension_width": int,
|
"dimension_width": int,
|
||||||
"dimension_height": int,
|
"dimension_height": int,
|
||||||
"modes": List[Mode],
|
"props": Props,
|
||||||
|
"resolution_modes": List[ResolutionMode],
|
||||||
"rotation": str,
|
"rotation": str,
|
||||||
"reflection": str,
|
"reflection": str,
|
||||||
},
|
},
|
||||||
@ -307,12 +294,13 @@ try:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
Screen = Dict[str, Union[int, str]]
|
EdidModel = Dict[str, str]
|
||||||
Device = Dict[str, Union[str, int, bool]]
|
Props = Dict[str, Union[List[str], EdidModel]]
|
||||||
Frequency = Dict[str, Union[float, bool]]
|
Frequency = Dict[str, Union[float, bool]]
|
||||||
Mode = Dict[str, Union[int, bool, List[Frequency]]]
|
ResolutionMode = Dict[str, Union[int, bool, List[Frequency]]]
|
||||||
Model = Dict[str, str]
|
Device = Dict[str, Union[str, int, bool, List[ResolutionMode]]]
|
||||||
Response = Dict[str, Union[Device, Mode, Screen]]
|
Screen = Dict[str, Union[int, List[Device]]]
|
||||||
|
Response = Dict[str, Screen]
|
||||||
|
|
||||||
|
|
||||||
_screen_pattern = (
|
_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)
|
# eDP1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis)
|
||||||
# 310mm x 170mm
|
# 310mm x 170mm
|
||||||
# regex101 demo link
|
# regex101 demo link
|
||||||
@ -365,25 +326,106 @@ _device_pattern = (
|
|||||||
+ r"( ?((?P<dimension_width>\d+)mm x (?P<dimension_height>\d+)mm)?)?"
|
+ 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)
|
class LineType(Enum):
|
||||||
if not result:
|
Screen = 1
|
||||||
next_lines.append(next_line)
|
Device = 2
|
||||||
return None
|
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 = {
|
device: Device = {
|
||||||
"modes": [],
|
"props": defaultdict(list),
|
||||||
|
"resolution_modes": [],
|
||||||
"is_connected": matches["is_connected"] == "connected",
|
"is_connected": matches["is_connected"] == "connected",
|
||||||
"is_primary": matches["is_primary"] is not None
|
"is_primary": matches["is_primary"] is not None
|
||||||
and len(matches["is_primary"]) > 0,
|
and len(matches["is_primary"]) > 0,
|
||||||
@ -403,97 +445,20 @@ def _parse_device(next_lines: List[str], quiet: bool = False) -> Optional[Device
|
|||||||
if v:
|
if v:
|
||||||
device[k] = int(v)
|
device[k] = int(v)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if not quiet:
|
raise Exception([f"{line.s} : {k} - {v} is not int-able"])
|
||||||
jc.utils.warning_message(
|
|
||||||
[f"{next_line} : {k} - {v} is not int-able"]
|
|
||||||
)
|
|
||||||
|
|
||||||
model: Optional[Model] = _parse_model(next_lines, quiet)
|
|
||||||
if model:
|
|
||||||
device["model_name"] = model["name"]
|
|
||||||
device["product_id"] = model["product_id"]
|
|
||||||
device["serial_number"] = model["serial_number"]
|
|
||||||
|
|
||||||
while next_lines:
|
|
||||||
next_line = next_lines.pop()
|
|
||||||
next_mode: Optional[Mode] = _parse_mode(next_line)
|
|
||||||
if next_mode:
|
|
||||||
device["modes"].append(next_mode)
|
|
||||||
else:
|
|
||||||
if re.match(_device_pattern, next_line):
|
|
||||||
next_lines.append(next_line)
|
|
||||||
break
|
|
||||||
return device
|
return device
|
||||||
|
|
||||||
|
|
||||||
# EDID:
|
def _parse_resolution_mode(line: _Line) -> ResolutionMode:
|
||||||
# 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)
|
|
||||||
frequencies: List[Frequency] = []
|
frequencies: List[Frequency] = []
|
||||||
|
|
||||||
if not result:
|
d = line.m.groupdict()
|
||||||
return None
|
|
||||||
|
|
||||||
d = result.groupdict()
|
|
||||||
resolution_width = int(d["resolution_width"])
|
resolution_width = int(d["resolution_width"])
|
||||||
resolution_height = int(d["resolution_height"])
|
resolution_height = int(d["resolution_height"])
|
||||||
is_high_resolution = d["is_high_resolution"] is not None
|
is_high_resolution = d["is_high_resolution"] is not None
|
||||||
|
|
||||||
mode: Mode = {
|
mode: ResolutionMode = {
|
||||||
"resolution_width": resolution_width,
|
"resolution_width": resolution_width,
|
||||||
"resolution_height": resolution_height,
|
"resolution_height": resolution_height,
|
||||||
"is_high_resolution": is_high_resolution,
|
"is_high_resolution": is_high_resolution,
|
||||||
@ -518,7 +483,45 @@ def _parse_mode(line: str) -> Optional[Mode]:
|
|||||||
return 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
|
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.compatibility(__name__, info.compatible, quiet)
|
||||||
jc.utils.input_type_check(data)
|
jc.utils.input_type_check(data)
|
||||||
|
|
||||||
linedata = data.splitlines()
|
index = 0
|
||||||
linedata.reverse() # For popping
|
lines = data.splitlines()
|
||||||
result: Dict = {}
|
screen, device = None, None
|
||||||
|
|
||||||
|
result: Response = {"screens": []}
|
||||||
if jc.utils.has_data(data):
|
if jc.utils.has_data(data):
|
||||||
result = {"screens": []}
|
while index < len(lines):
|
||||||
while linedata:
|
line = _Line.categorize(lines[index])
|
||||||
screen = _parse_screen(linedata)
|
if line.t == LineType.Screen:
|
||||||
if screen:
|
screen = _parse_screen(line)
|
||||||
result["screens"].append(screen)
|
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
|
return result
|
||||||
|
@ -12,6 +12,7 @@ from functools import lru_cache
|
|||||||
from typing import Any, List, Dict, Iterable, Union, Optional, TextIO
|
from typing import Any, List, Dict, Iterable, Union, Optional, TextIO
|
||||||
from .jc_types import TimeStampFormatType
|
from .jc_types import TimeStampFormatType
|
||||||
|
|
||||||
|
CLI_QUIET = False
|
||||||
|
|
||||||
def _asciify(string: str) -> str:
|
def _asciify(string: str) -> str:
|
||||||
"""
|
"""
|
||||||
@ -62,6 +63,9 @@ def warning_message(message_lines: List[str]) -> None:
|
|||||||
|
|
||||||
None - just prints output to STDERR
|
None - just prints output to STDERR
|
||||||
"""
|
"""
|
||||||
|
if CLI_QUIET:
|
||||||
|
return
|
||||||
|
|
||||||
# this is for backwards compatibility with existing custom parsers
|
# this is for backwards compatibility with existing custom parsers
|
||||||
if isinstance(message_lines, str):
|
if isinstance(message_lines, str):
|
||||||
message_lines = [message_lines]
|
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
|
.SH NAME
|
||||||
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types,
|
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types,
|
||||||
and strings
|
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(
|
setuptools.setup(
|
||||||
name='jc',
|
name='jc',
|
||||||
version='1.25.0',
|
version='1.25.1',
|
||||||
author='Kelly Brazil',
|
author='Kelly Brazil',
|
||||||
author_email='kellyjonbrazil@gmail.com',
|
author_email='kellyjonbrazil@gmail.com',
|
||||||
description='Converts the output of popular command-line tools and file-types to JSON.',
|
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:
|
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()
|
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
|
# output
|
||||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-test.json'), 'r', encoding='utf-8') as f:
|
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())
|
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:
|
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())
|
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):
|
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)
|
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):
|
def test_ini_duplicate_keys(self):
|
||||||
"""
|
"""
|
||||||
Test input that contains duplicate keys. Only the last value should be used.
|
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)
|
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__':
|
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:
|
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()
|
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
|
# output
|
||||||
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-dup-test.json'), 'r', encoding='utf-8') as f:
|
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())
|
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:
|
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())
|
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):
|
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)
|
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):
|
def test_ini_dup_duplicate_keys(self):
|
||||||
"""
|
"""
|
||||||
Test input that contains duplicate keys.
|
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)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pygments
|
import pygments
|
||||||
from pygments.token import (Name, Number, String, Keyword)
|
from pygments.token import (Name, Number, String, Keyword)
|
||||||
PYGMENTS_INSTALLED=True
|
PYGMENTS_INSTALLED=True
|
||||||
except ModuleNotFoundError:
|
except:
|
||||||
PYGMENTS_INSTALLED=False
|
PYGMENTS_INSTALLED=False
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ruamel.yaml
|
||||||
|
RUAMELYAML_INSTALLED = True
|
||||||
|
except:
|
||||||
|
RUAMELYAML_INSTALLED = False
|
||||||
|
|
||||||
from jc.cli import JcCli
|
from jc.cli import JcCli
|
||||||
import jc.parsers.url as url_parser
|
import jc.parsers.url as url_parser
|
||||||
import jc.parsers.proc as proc_parser
|
import jc.parsers.proc as proc_parser
|
||||||
@ -47,8 +55,8 @@ class MyTests(unittest.TestCase):
|
|||||||
resulting_attributes = (cli.magic_found_parser, cli.magic_options, cli.magic_run_command)
|
resulting_attributes = (cli.magic_found_parser, cli.magic_options, cli.magic_run_command)
|
||||||
self.assertEqual(expected, resulting_attributes)
|
self.assertEqual(expected, resulting_attributes)
|
||||||
|
|
||||||
|
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||||
def test_cli_set_env_colors(self):
|
def test_cli_set_env_colors(self):
|
||||||
if PYGMENTS_INSTALLED:
|
|
||||||
if pygments.__version__.startswith('2.3.'):
|
if pygments.__version__.startswith('2.3.'):
|
||||||
env = {
|
env = {
|
||||||
'': {
|
'': {
|
||||||
@ -146,8 +154,8 @@ class MyTests(unittest.TestCase):
|
|||||||
cli.set_custom_colors()
|
cli.set_custom_colors()
|
||||||
self.assertEqual(cli.custom_colors, expected_colors)
|
self.assertEqual(cli.custom_colors, expected_colors)
|
||||||
|
|
||||||
|
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||||
def test_cli_json_out(self):
|
def test_cli_json_out(self):
|
||||||
if PYGMENTS_INSTALLED:
|
|
||||||
test_input = [
|
test_input = [
|
||||||
None,
|
None,
|
||||||
{},
|
{},
|
||||||
@ -180,8 +188,8 @@ class MyTests(unittest.TestCase):
|
|||||||
cli.data_out = test_dict
|
cli.data_out = test_dict
|
||||||
self.assertEqual(cli.json_out(), expected_json)
|
self.assertEqual(cli.json_out(), expected_json)
|
||||||
|
|
||||||
|
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||||
def test_cli_json_out_mono(self):
|
def test_cli_json_out_mono(self):
|
||||||
if PYGMENTS_INSTALLED:
|
|
||||||
test_input = [
|
test_input = [
|
||||||
None,
|
None,
|
||||||
{},
|
{},
|
||||||
@ -205,6 +213,7 @@ class MyTests(unittest.TestCase):
|
|||||||
cli.data_out = test_dict
|
cli.data_out = test_dict
|
||||||
self.assertEqual(cli.json_out(), expected_json)
|
self.assertEqual(cli.json_out(), expected_json)
|
||||||
|
|
||||||
|
@unittest.skipIf(not PYGMENTS_INSTALLED, 'pygments library not installed')
|
||||||
def test_cli_json_out_pretty(self):
|
def test_cli_json_out_pretty(self):
|
||||||
test_input = [
|
test_input = [
|
||||||
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
{"key1": "value1", "key2": 2, "key3": None, "key4": 3.14, "key5": True},
|
||||||
@ -229,8 +238,27 @@ class MyTests(unittest.TestCase):
|
|||||||
cli.data_out = test_dict
|
cli.data_out = test_dict
|
||||||
self.assertEqual(cli.json_out(), expected_json)
|
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):
|
def test_cli_yaml_out(self):
|
||||||
if PYGMENTS_INSTALLED:
|
|
||||||
test_input = [
|
test_input = [
|
||||||
None,
|
None,
|
||||||
{},
|
{},
|
||||||
@ -263,6 +291,7 @@ class MyTests(unittest.TestCase):
|
|||||||
cli.data_out = test_dict
|
cli.data_out = test_dict
|
||||||
self.assertEqual(cli.yaml_out(), expected_json)
|
self.assertEqual(cli.yaml_out(), expected_json)
|
||||||
|
|
||||||
|
@unittest.skipIf(not RUAMELYAML_INSTALLED, 'ruamel.yaml library not installed')
|
||||||
def test_cli_yaml_out_mono(self):
|
def test_cli_yaml_out_mono(self):
|
||||||
test_input = [
|
test_input = [
|
||||||
None,
|
None,
|
||||||
@ -295,6 +324,10 @@ class MyTests(unittest.TestCase):
|
|||||||
self.assertGreaterEqual(cli.about_jc()['parser_count'], 55)
|
self.assertGreaterEqual(cli.about_jc()['parser_count'], 55)
|
||||||
self.assertEqual(cli.about_jc()['parser_count'], len(cli.about_jc()['parsers']))
|
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):
|
def test_add_meta_to_simple_dict(self):
|
||||||
cli = JcCli()
|
cli = JcCli()
|
||||||
cli.data_out = {'a': 1, 'b': 2}
|
cli.data_out = {'a': 1, 'b': 2}
|
||||||
|
@ -11,6 +11,12 @@ class MyTests(unittest.TestCase):
|
|||||||
p = jc.lib.get_parser('arp')
|
p = jc.lib.get_parser('arp')
|
||||||
self.assertIsInstance(p, ModuleType)
|
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):
|
def test_lib_get_parser_module(self):
|
||||||
p = jc.lib.get_parser(csv_parser)
|
p = jc.lib.get_parser(csv_parser)
|
||||||
self.assertIsInstance(p, ModuleType)
|
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)
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -2,7 +2,6 @@ import os
|
|||||||
import unittest
|
import unittest
|
||||||
import json
|
import json
|
||||||
import jc.parsers.xml
|
import jc.parsers.xml
|
||||||
import xmltodict
|
|
||||||
|
|
||||||
# fix for whether tests are run directly or via runtests.sh
|
# fix for whether tests are run directly or via runtests.sh
|
||||||
try:
|
try:
|
||||||
@ -10,10 +9,18 @@ try:
|
|||||||
except:
|
except:
|
||||||
from _vendor.packaging import version # type: ignore
|
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__))
|
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):
|
class MyTests(unittest.TestCase):
|
||||||
|
|
||||||
# input
|
# input
|
||||||
|
@ -1,36 +1,33 @@
|
|||||||
|
import pprint
|
||||||
import re
|
import re
|
||||||
import unittest
|
import unittest
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from jc.parsers.xrandr import (
|
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,
|
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):
|
class XrandrTests(unittest.TestCase):
|
||||||
def setUp(self):
|
|
||||||
jc.parsers.xrandr.parse_state = {}
|
|
||||||
|
|
||||||
def test_xrandr_nodata(self):
|
def test_xrandr_nodata(self):
|
||||||
"""
|
"""
|
||||||
Test 'xrandr' with no data
|
Test 'xrandr' with no data
|
||||||
"""
|
"""
|
||||||
self.assertEqual(parse("", quiet=True), {})
|
self.assertEqual(parse("", quiet=True), {"screens": []})
|
||||||
|
|
||||||
def test_regexes(self):
|
def test_regexes(self):
|
||||||
devices = [
|
devices = [
|
||||||
@ -61,37 +58,30 @@ class XrandrTests(unittest.TestCase):
|
|||||||
"1400x900 59.96 59.88",
|
"1400x900 59.96 59.88",
|
||||||
]
|
]
|
||||||
for mode in modes:
|
for mode in modes:
|
||||||
match = re.match(_mode_pattern, mode)
|
match = re.match(_resolution_mode_pattern, mode)
|
||||||
self.assertIsNotNone(match)
|
self.assertIsNotNone(match)
|
||||||
if match:
|
if match:
|
||||||
rest = match.groupdict()["rest"]
|
rest = match.groupdict()["rest"]
|
||||||
self.assertIsNotNone(re.match(_frequencies_pattern, rest))
|
self.assertIsNotNone(re.match(_frequencies_pattern, rest))
|
||||||
|
|
||||||
edid_lines = [
|
def test_line_categorize(self):
|
||||||
" EDID: ",
|
base = "eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm"
|
||||||
" 00ffffffffffff000469d41901010101 ",
|
resolution_mode = " 320x240 60.05"
|
||||||
" 2011010308291a78ea8585a6574a9c26 ",
|
prop_key = " EDID:"
|
||||||
" 125054bfef80714f8100810f81408180 ",
|
prop_value = " 00ffffffffffff0006af3d5700000000"
|
||||||
" 9500950f01019a29a0d0518422305098 ",
|
invalid = ""
|
||||||
" 360098ff1000001c000000fd00374b1e ",
|
|
||||||
" 530f000a202020202020000000fc0041 ",
|
|
||||||
" 535553205657313933530a20000000ff ",
|
|
||||||
" 0037384c383032313130370a20200077 ",
|
|
||||||
]
|
|
||||||
|
|
||||||
for i in range(len(edid_lines)):
|
self.assertEqual(LineType.Device, _Line.categorize(base).t)
|
||||||
line = edid_lines[i]
|
self.assertEqual(LineType.ResolutionMode, _Line.categorize(resolution_mode).t)
|
||||||
if i == 0:
|
self.assertEqual(LineType.PropKey, _Line.categorize(prop_key).t)
|
||||||
match = re.match(_edid_head_pattern, line)
|
self.assertEqual(LineType.PropValue, _Line.categorize(prop_value).t)
|
||||||
else:
|
with self.assertRaises(Exception):
|
||||||
match = re.match(_edid_line_pattern, line)
|
_Line.categorize(invalid)
|
||||||
|
|
||||||
self.assertIsNotNone(match)
|
|
||||||
|
|
||||||
def test_screens(self):
|
def test_screens(self):
|
||||||
sample = "Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767"
|
sample = "Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767"
|
||||||
|
line = _Line.categorize(sample)
|
||||||
actual: Optional[Screen] = _parse_screen([sample])
|
actual: Optional[Screen] = _parse_screen(line)
|
||||||
self.assertIsNotNone(actual)
|
self.assertIsNotNone(actual)
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
@ -110,7 +100,8 @@ class XrandrTests(unittest.TestCase):
|
|||||||
sample = (
|
sample = (
|
||||||
"Screen 0: minimum 320 x 200, current 1920 x 1080, maximum 16384 x 16384"
|
"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:
|
if actual:
|
||||||
self.assertEqual(320, actual["minimum_width"])
|
self.assertEqual(320, actual["minimum_width"])
|
||||||
else:
|
else:
|
||||||
@ -119,7 +110,8 @@ class XrandrTests(unittest.TestCase):
|
|||||||
def test_device(self):
|
def test_device(self):
|
||||||
# regex101 sample link for tests/edits https://regex101.com/r/3cHMv3/1
|
# 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"
|
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 = {
|
expected = {
|
||||||
"device_name": "eDP1",
|
"device_name": "eDP1",
|
||||||
@ -140,17 +132,19 @@ class XrandrTests(unittest.TestCase):
|
|||||||
for k, v in expected.items():
|
for k, v in expected.items():
|
||||||
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
||||||
|
|
||||||
with open("tests/fixtures/generic/xrandr_device.out", "r") as f:
|
# with open("tests/fixtures/generic/xrandr_device.out", "r") as f:
|
||||||
extended_sample = f.read().splitlines()
|
# extended_sample = f.read().splitlines()
|
||||||
extended_sample.reverse()
|
|
||||||
|
|
||||||
device = _parse_device(extended_sample)
|
# device = _parse_device(extended_sample)
|
||||||
if device:
|
# if device:
|
||||||
self.assertEqual(59.94, device["modes"][12]["frequencies"][4]["frequency"])
|
# self.assertEqual(
|
||||||
|
# 59.94, device["resolution_modes"][12]["frequencies"][4]["frequency"]
|
||||||
|
# )
|
||||||
|
|
||||||
def test_device_with_reflect(self):
|
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"
|
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 = {
|
expected = {
|
||||||
"device_name": "VGA-1",
|
"device_name": "VGA-1",
|
||||||
@ -173,7 +167,7 @@ class XrandrTests(unittest.TestCase):
|
|||||||
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
self.assertEqual(v, actual[k], f"Devices regex failed on {k}")
|
||||||
|
|
||||||
def test_mode(self):
|
def test_mode(self):
|
||||||
sample_1 = "1920x1080 60.03*+ 59.93"
|
sample_1 = " 1920x1080 60.03*+ 59.93"
|
||||||
expected = {
|
expected = {
|
||||||
"frequencies": [
|
"frequencies": [
|
||||||
{"frequency": 60.03, "is_current": True, "is_preferred": True},
|
{"frequency": 60.03, "is_current": True, "is_preferred": True},
|
||||||
@ -183,7 +177,8 @@ class XrandrTests(unittest.TestCase):
|
|||||||
"resolution_height": 1080,
|
"resolution_height": 1080,
|
||||||
"is_high_resolution": False,
|
"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)
|
self.assertIsNotNone(actual)
|
||||||
|
|
||||||
@ -192,7 +187,8 @@ class XrandrTests(unittest.TestCase):
|
|||||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||||
|
|
||||||
sample_2 = " 1920x1080i 60.00 50.00 59.94"
|
sample_2 = " 1920x1080i 60.00 50.00 59.94"
|
||||||
actual: Optional[Mode] = _parse_mode(sample_2)
|
line = _Line.categorize(sample_2)
|
||||||
|
actual: Optional[ResolutionMode] = _parse_resolution_mode(line)
|
||||||
self.assertIsNotNone(actual)
|
self.assertIsNotNone(actual)
|
||||||
if actual:
|
if actual:
|
||||||
self.assertEqual(True, actual["is_high_resolution"])
|
self.assertEqual(True, actual["is_high_resolution"])
|
||||||
@ -205,7 +201,9 @@ class XrandrTests(unittest.TestCase):
|
|||||||
actual = parse(txt, quiet=True)
|
actual = parse(txt, quiet=True)
|
||||||
|
|
||||||
self.assertEqual(1, len(actual["screens"]))
|
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):
|
def test_complete_2(self):
|
||||||
with open("tests/fixtures/generic/xrandr_2.out", "r") as f:
|
with open("tests/fixtures/generic/xrandr_2.out", "r") as f:
|
||||||
@ -213,7 +211,9 @@ class XrandrTests(unittest.TestCase):
|
|||||||
actual = parse(txt, quiet=True)
|
actual = parse(txt, quiet=True)
|
||||||
|
|
||||||
self.assertEqual(1, len(actual["screens"]))
|
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):
|
def test_complete_3(self):
|
||||||
with open("tests/fixtures/generic/xrandr_3.out", "r") as f:
|
with open("tests/fixtures/generic/xrandr_3.out", "r") as f:
|
||||||
@ -232,84 +232,119 @@ class XrandrTests(unittest.TestCase):
|
|||||||
actual = parse(txt, quiet=True)
|
actual = parse(txt, quiet=True)
|
||||||
|
|
||||||
self.assertEqual(1, len(actual["screens"]))
|
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):
|
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()
|
txt = f.read()
|
||||||
actual = parse(txt, quiet=True)
|
actual = parse(txt, quiet=True)
|
||||||
|
|
||||||
self.assertEqual(1, len(actual["screens"]))
|
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):
|
# def test_model(self):
|
||||||
asus_edid = [
|
# asus_edid = [
|
||||||
" EDID: ",
|
# " EDID: ",
|
||||||
" 00ffffffffffff000469d41901010101",
|
# " 00ffffffffffff000469d41901010101",
|
||||||
" 2011010308291a78ea8585a6574a9c26",
|
# " 2011010308291a78ea8585a6574a9c26",
|
||||||
" 125054bfef80714f8100810f81408180",
|
# " 125054bfef80714f8100810f81408180",
|
||||||
" 9500950f01019a29a0d0518422305098",
|
# " 9500950f01019a29a0d0518422305098",
|
||||||
" 360098ff1000001c000000fd00374b1e",
|
# " 360098ff1000001c000000fd00374b1e",
|
||||||
" 530f000a202020202020000000fc0041",
|
# " 530f000a202020202020000000fc0041",
|
||||||
" 535553205657313933530a20000000ff",
|
# " 535553205657313933530a20000000ff",
|
||||||
" 0037384c383032313130370a20200077",
|
# " 0037384c383032313130370a20200077",
|
||||||
]
|
# ]
|
||||||
asus_edid.reverse()
|
# asus_edid.reverse()
|
||||||
|
|
||||||
expected = {
|
# expected = {
|
||||||
"name": "ASUS VW193S",
|
# "name": "ASUS VW193S",
|
||||||
"product_id": "6612",
|
# "product_id": "6612",
|
||||||
"serial_number": "78L8021107",
|
# "serial_number": "78L8021107",
|
||||||
}
|
# }
|
||||||
|
|
||||||
actual: Optional[Model] = _parse_model(asus_edid)
|
# actual: Optional[EdidModel] = _parse_model(asus_edid)
|
||||||
self.assertIsNotNone(actual)
|
# self.assertIsNotNone(actual)
|
||||||
|
|
||||||
if actual:
|
# if actual:
|
||||||
for k, v in expected.items():
|
# for k, v in expected.items():
|
||||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
# self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||||
|
|
||||||
generic_edid = [
|
# generic_edid = [
|
||||||
" EDID: ",
|
# " EDID: ",
|
||||||
" 00ffffffffffff004ca3523100000000",
|
# " 00ffffffffffff004ca3523100000000",
|
||||||
" 0014010380221378eac8959e57549226",
|
# " 0014010380221378eac8959e57549226",
|
||||||
" 0f505400000001010101010101010101",
|
# " 0f505400000001010101010101010101",
|
||||||
" 010101010101381d56d4500016303020",
|
# " 010101010101381d56d4500016303020",
|
||||||
" 250058c2100000190000000f00000000",
|
# " 250058c2100000190000000f00000000",
|
||||||
" 000000000025d9066a00000000fe0053",
|
# " 000000000025d9066a00000000fe0053",
|
||||||
" 414d53554e470a204ca34154000000fe",
|
# " 414d53554e470a204ca34154000000fe",
|
||||||
" 004c544e313536415432343430310018",
|
# " 004c544e313536415432343430310018",
|
||||||
]
|
# ]
|
||||||
generic_edid.reverse()
|
# generic_edid.reverse()
|
||||||
|
|
||||||
expected = {
|
# expected = {
|
||||||
"name": "Generic",
|
# "name": "Generic",
|
||||||
"product_id": "12626",
|
# "product_id": "12626",
|
||||||
"serial_number": "0",
|
# "serial_number": "0",
|
||||||
}
|
# }
|
||||||
|
|
||||||
jc.parsers.xrandr.parse_state = {}
|
# jc.parsers.xrandr.parse_state = {}
|
||||||
actual: Optional[Model] = _parse_model(generic_edid)
|
# actual: Optional[EdidModel] = _parse_model(generic_edid)
|
||||||
self.assertIsNotNone(actual)
|
# self.assertIsNotNone(actual)
|
||||||
|
|
||||||
if actual:
|
# if actual:
|
||||||
for k, v in expected.items():
|
# for k, v in expected.items():
|
||||||
self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
# self.assertEqual(v, actual[k], f"mode regex failed on {k}")
|
||||||
|
|
||||||
empty_edid = [""]
|
|
||||||
actual: Optional[Model] = _parse_model(empty_edid)
|
|
||||||
self.assertIsNone(actual)
|
|
||||||
|
|
||||||
|
# empty_edid = [""]
|
||||||
|
# actual: Optional[EdidModel] = _parse_model(empty_edid)
|
||||||
|
# self.assertIsNone(actual)
|
||||||
|
|
||||||
def test_issue_490(self):
|
def test_issue_490(self):
|
||||||
"""test for issue 490: https://github.com/kellyjonbrazil/jc/issues/490"""
|
"""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
|
Screen 0: minimum 1024 x 600, current 1024 x 600, maximum 1024 x 600
|
||||||
default connected 1024x600+0+0 0mm x 0mm
|
default connected 1024x600+0+0 0mm x 0mm
|
||||||
1024x600 0.00*
|
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}]}
|
actual: Response = parse(data_in)
|
||||||
self.assertEqual(jc.parsers.xrandr.parse(data_in), expected)
|
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__":
|
if __name__ == "__main__":
|
||||||
|
@ -5,7 +5,14 @@ import jc.parsers.yaml
|
|||||||
|
|
||||||
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
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):
|
class MyTests(unittest.TestCase):
|
||||||
|
|
||||||
# input
|
# input
|
||||||
|
Reference in New Issue
Block a user