1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2025-06-17 00:07:37 +02:00

Merge pull request #256 from kellyjonbrazil/dev

v1.20.1
This commit is contained in:
Kelly Brazil
2022-06-15 22:16:07 +00:00
committed by GitHub
33 changed files with 1202 additions and 135 deletions

View File

@ -1,5 +1,13 @@
jc changelog
20220615 v1.20.1
- Add `postconf -M` parser tested on linux
- Update `asciitable` and `asciitable-m` parsers to preserve case in key
names when using the `-r` or `raw=True` options.
- Add long options (e.g. `--help`, `--about`, `--pretty`, etc.)
- Add shell completions for Bash and Zsh
- Fix `id` parser for cases where the user or group name is not present
20220531 v1.20.0
- Add YAML output option with `-y`
- Add `top -b` standard and streaming parsers tested on linux

View File

@ -2728,6 +2728,36 @@ pip show wrapt wheel | jc --pip-show -p # or: jc -p pip show wrapt whe
}
]
```
### postconf -M
```bash
postconf -M | jc --postconf -p # or jc -p postconf -M
```
```json
[
{
"service_name": "smtp",
"service_type": "inet",
"private": false,
"unprivileged": null,
"chroot": true,
"wake_up_time": null,
"process_limit": null,
"command": "smtpd",
"no_wake_up_before_first_use": null
},
{
"service_name": "pickup",
"service_type": "unix",
"private": false,
"unprivileged": null,
"chroot": true,
"wake_up_time": 60,
"process_limit": 1,
"command": "pickup",
"no_wake_up_before_first_use": false
}
]
```
### ps
```bash
ps -ef | jc --ps -p # or: jc -p ps -ef
@ -3465,6 +3495,105 @@ timedatectl | jc --timedatectl -p # or: jc -p timedatectl
"epoch_utc": 1583888001
}
```
### tob -b
```bash
top -b -n 1 | jc --top -p # or jc -p tob -b -n 1
```
```json
[
{
"time": "11:20:43",
"uptime": 118,
"users": 2,
"load_1m": 0.0,
"load_5m": 0.01,
"load_15m": 0.05,
"tasks_total": 108,
"tasks_running": 2,
"tasks_sleeping": 106,
"tasks_stopped": 0,
"tasks_zombie": 0,
"cpu_user": 5.6,
"cpu_sys": 11.1,
"cpu_nice": 0.0,
"cpu_idle": 83.3,
"cpu_wait": 0.0,
"cpu_hardware": 0.0,
"cpu_software": 0.0,
"cpu_steal": 0.0,
"mem_total": 3.7,
"mem_free": 3.3,
"mem_used": 0.2,
"mem_buff_cache": 0.2,
"swap_total": 2.0,
"swap_free": 2.0,
"swap_used": 0.0,
"mem_available": 3.3,
"processes": [
{
"pid": 2225,
"user": "kbrazil",
"priority": 20,
"nice": 0,
"virtual_mem": 158.1,
"resident_mem": 2.2,
"shared_mem": 1.6,
"status": "running",
"percent_cpu": 12.5,
"percent_mem": 0.1,
"time_hundredths": "0:00.02",
"command": "top",
"parent_pid": 1884,
"uid": 1000,
"real_uid": 1000,
"real_user": "kbrazil",
"saved_uid": 1000,
"saved_user": "kbrazil",
"gid": 1000,
"group": "kbrazil",
"pgrp": 2225,
"tty": "pts/0",
"tty_process_gid": 2225,
"session_id": 1884,
"thread_count": 1,
"last_used_processor": 0,
"time": "0:00",
"swap": 0.0,
"code": 0.1,
"data": 1.0,
"major_page_fault_count": 0,
"minor_page_fault_count": 736,
"dirty_pages_count": 0,
"sleeping_in_function": null,
"flags": "..4.2...",
"cgroups": "1:name=systemd:/user.slice/user-1000.+",
"supplementary_gids": [
10,
1000
],
"supplementary_groups": [
"wheel",
"kbrazil"
],
"thread_gid": 2225,
"environment_variables": [
"XDG_SESSION_ID=2",
"HOSTNAME=localhost"
],
"major_page_fault_count_delta": 0,
"minor_page_fault_count_delta": 4,
"used": 2.2,
"ipc_namespace_inode": 4026531839,
"mount_namespace_inode": 4026531840,
"net_namespace_inode": 4026531956,
"pid_namespace_inode": 4026531836,
"user_namespace_inode": 4026531837,
"nts_namespace_inode": 4026531838
}
]
}
]
```
### tracepath
```bash
tracepath6 3ffe:2400:0:109::2 | jc --tracepath -p

View File

@ -210,6 +210,7 @@ option.
| ` --ping-s` | `ping` and `ping6` command streaming parser | [📃](https://kellyjonbrazil.github.io/jc/docs/parsers/ping_s) |
| ` --pip-list` | `pip list` command parser | [📃](https://kellyjonbrazil.github.io/jc/docs/parsers/pip_list) |
| ` --pip-show` | `pip show` command parser | [📃](https://kellyjonbrazil.github.io/jc/docs/parsers/pip_show) |
| ` --postconf` | `postconf -M` command parser | [📃](https://kellyjonbrazil.github.io/jc/docs/parsers/postconf) |
| ` --ps` | `ps` command parser | [📃](https://kellyjonbrazil.github.io/jc/docs/parsers/ps) |
| ` --route` | `route` command parser | [📃](https://kellyjonbrazil.github.io/jc/docs/parsers/route) |
| ` --rpm-qi` | `rpm -qi` command parser | [📃](https://kellyjonbrazil.github.io/jc/docs/parsers/rpm_qi) |
@ -250,22 +251,21 @@ option.
| ` --zipinfo` | `zipinfo` command parser | [📃](https://kellyjonbrazil.github.io/jc/docs/parsers/zipinfo) |
### Options
- `-a` about `jc`. Prints information about `jc` and the parsers (in JSON or
YAML, of course!)
- `-C` force color output even when using pipes (overrides `-m` and the
`NO_COLOR` env variable)
- `-d` debug mode. Prints trace messages if parsing issues are encountered (use
`-dd` for verbose debugging)
- `-h` help. Use `jc -h --parser_name` for parser documentation
- `-m` monochrome JSON output
- `-p` pretty format the JSON output
- `-q` quiet mode. Suppresses parser warning messages (use `-qq` to ignore
streaming parser errors)
- `-r` raw output. Provides a more literal JSON output, typically with string
values and no additional semantic processing
- `-u` unbuffer output
- `-v` version information
- `-y` YAML output
| Short | Long | Description |
|-------|-----------------|--------------------------------------------------------------------------------------------------------------|
| `-a` | `--about` | About `jc`. Prints information about `jc` and the parsers (in JSON or YAML, of course!) |
| `-C` | `--force-color` | Force color output even when using pipes (overrides `-m` and the `NO_COLOR` env variable) |
| `-d` | `--debug` | Debug mode. Prints trace messages if parsing issues are encountered (use`-dd` for verbose debugging) |
| `-h` | `--help` | Help. Use `jc -h --parser_name` for parser documentation |
| `-m` | `--monochrome` | Monochrome output |
| `-p` | `--pretty` | Pretty format the JSON output |
| `-q` | `--quiet` | Quiet mode. Suppresses parser warning messages (use `-qq` to ignore streaming parser errors) |
| `-r` | `--raw` | Raw output. Provides more literal output, typically with string values and no additional semantic processing |
| `-u` | `--unbuffer` | Unbuffer output |
| `-v` | `--version` | Version information |
| `-y` | `--yaml-out` | YAML output |
| `-B` | `--bash-comp` | Generate Bash shell completion script |
| `-Z` | `--zsh-comp` | Generate Zsh shell completion script |
### Exit Codes
Any fatal errors within `jc` will generate an exit code of `100`, otherwise the

View File

@ -59,6 +59,9 @@ etc...
Headers (keys) are converted to snake-case. All values are returned as
strings, except empty strings, which are converted to None/null.
> Note: To preserve the case of the keys use the `-r` cli option or
> `raw=True` argument in `parse()`.
Usage (cli):
$ cat table.txt | jc --asciitable
@ -141,4 +144,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@ -29,6 +29,9 @@ Headers (keys) are converted to snake-case and newlines between multi-line
headers are joined with an underscore. All values are returned as strings,
except empty strings, which are converted to None/null.
> Note: To preserve the case of the keys use the `-r` cli option or
> `raw=True` argument in `parse()`.
> Note: table column separator characters (e.g. `|`) cannot be present
> inside the cell data. If detected, a warning message will be printed to
> `STDERR` and the line will be skipped. The warning message can be
@ -126,4 +129,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.1 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.2 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

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

View File

@ -11,9 +11,10 @@ Parses standard `INI` files and files containing simple key/value pairs.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If duplicate keys are found, only the last value will be used.
> Note: Values starting and ending with quotation marks will have the marks
> removed. If you would like to keep the quotation marks, use the `-r`
> command-line argument or the `raw=True` argument in `parse()`.
> Note: Values starting and ending with double or single quotation marks
> will have the marks removed. If you would like to keep the quotation
> marks, use the `-r` command-line argument or the `raw=True` argument in
> `parse()`.
Usage (cli):
@ -91,4 +92,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.6 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.7 by Kelly Brazil (kellyjonbrazil@gmail.com)

115
docs/parsers/postconf.md Normal file
View File

@ -0,0 +1,115 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.postconf"></a>
# jc.parsers.postconf
jc - JSON Convert `postconf -M` command output parser
Usage (cli):
$ postconf -M | jc --postconf
or
$ jc postconf -M
Usage (module):
import jc
result = jc.parse('postconf', postconf_command_output)
Schema:
[
{
"service_name": string,
"service_type": string,
"private": boolean/null, # [0]
"unprivileged": boolean/null, # [0]
"chroot": boolean/null, # [0]
"wake_up_time": integer/null, # [0]
"no_wake_up_before_first_use": boolean/null, # [1]
"process_limit": integer/null, # [0]
"command": string
}
]
[0] '-' converted to null/None
[1] null/None if `wake_up_time` is null/None
Examples:
$ postconf -M | jc --postconf -p
[
{
"service_name": "smtp",
"service_type": "inet",
"private": false,
"unprivileged": null,
"chroot": true,
"wake_up_time": null,
"process_limit": null,
"command": "smtpd",
"no_wake_up_before_first_use": null
},
{
"service_name": "pickup",
"service_type": "unix",
"private": false,
"unprivileged": null,
"chroot": true,
"wake_up_time": 60,
"process_limit": 1,
"command": "pickup",
"no_wake_up_before_first_use": false
}
]
$ postconf -M | jc --postconf -p -r
[
{
"service_name": "smtp",
"service_type": "inet",
"private": "n",
"unprivileged": "-",
"chroot": "y",
"wake_up_time": "-",
"process_limit": "-",
"command": "smtpd"
},
{
"service_name": "pickup",
"service_type": "unix",
"private": "n",
"unprivileged": "-",
"chroot": "y",
"wake_up_time": "60",
"process_limit": "1",
"command": "pickup"
}
]
<a id="jc.parsers.postconf.parse"></a>
### parse
```python
def parse(data: str, raw: bool = False, quiet: bool = False) -> List[Dict]
```
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
### Parser Information
Compatibility: linux
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@ -9,10 +9,13 @@ import textwrap
import signal
import shlex
import subprocess
from typing import List, Dict
from .lib import (__version__, parser_info, all_parser_info, parsers,
_get_parser, _parser_is_streaming, standard_parser_mod_list,
plugin_parser_mod_list, streaming_parser_mod_list)
from . import utils
from .cli_data import long_options_map
from .shell_completions import bash_completion, zsh_completion
from . import tracebackplus
from .exceptions import LibraryNotInstalled, ParseError
@ -166,6 +169,22 @@ def parsers_text(indent=0, pad=0):
return ptext
def options_text(indent=0, pad=0):
"""Return the argument and description information from each option"""
otext = ''
padding_char = ' '
for option in long_options_map:
o_short = '-' + long_options_map[option][0]
o_desc = long_options_map[option][1]
o_combined = o_short + ', ' + option
padding = pad - len(o_combined)
indent_text = padding_char * indent
padding_text = padding_char * padding
otext += indent_text + o_combined + padding_text + o_desc + '\n'
return otext
def about_jc():
"""Return jc info and the contents of each parser.info as a dictionary"""
return {
@ -189,43 +208,35 @@ def about_jc():
def helptext():
"""Return the help text with the list of parsers"""
parsers_string = parsers_text(indent=12, pad=17)
parsers_string = parsers_text(indent=4, pad=20)
options_string = options_text(indent=4, pad=20)
helptext_string = f'''\
jc converts the output of many commands and file-types to JSON
jc converts the output of many commands and file-types to JSON or YAML
Usage: COMMAND | jc PARSER [OPTIONS]
Usage:
COMMAND | jc PARSER [OPTIONS]
or magic syntax:
or magic syntax:
jc [OPTIONS] COMMAND
jc [OPTIONS] COMMAND
Parsers:
Parsers:
{parsers_string}
Options:
-a about jc
-C force color output even when using pipes (overrides -m)
-d debug (-dd for verbose debug)
-h help (-h --parser_name for parser documentation)
-m monochrome output
-p pretty print output
-q quiet - suppress parser warnings (-qq to ignore streaming errors)
-r raw JSON output
-u unbuffer output
-v version info
-y YAML output
Options:
{options_string}
Examples:
Standard Syntax:
$ dig www.google.com | jc --dig --pretty
Examples:
Standard Syntax:
$ dig www.google.com | jc --dig -p
Magic Syntax:
$ jc --pretty dig www.google.com
Magic Syntax:
$ jc -p dig www.google.com
Parser Documentation:
$ jc --help --dig
'''
Parser Documentation:
$ jc -h --dig
'''
return textwrap.dedent(helptext_string)
return helptext_string
def help_doc(options):
@ -285,7 +296,7 @@ def yaml_out(data, pretty=False, env_colors=None, mono=False, piped_out=False, a
# ruamel.yaml versions prior to 0.17.0 the use of __file__ in the
# plugin code is incompatible with the pyoxidizer packager
YAML.official_plug_ins = lambda a: []
yaml=YAML()
yaml = YAML()
yaml.default_flow_style = False
yaml.explicit_start = True
yaml.allow_unicode = not ascii_only
@ -381,7 +392,7 @@ def magic_parser(args):
jc_options (list) list of jc options
"""
# bail immediately if there are no args or a parser is defined
if len(args) <= 1 or args[1].startswith('--'):
if len(args) <= 1 or (args[1].startswith('--') and args[1] not in long_options_map):
return False, None, None, []
args_given = args[1:]
@ -389,6 +400,12 @@ def magic_parser(args):
# find the options
for arg in list(args_given):
# long option found - populate option list
if arg in long_options_map:
options.extend(long_options_map[arg][0])
args_given.pop(0)
continue
# parser found - use standard syntax
if arg.startswith('--'):
return False, None, None, []
@ -483,6 +500,9 @@ def main():
# find options if magic_parser did not find a command
if not valid_command:
for opt in sys.argv:
if opt in long_options_map:
options.extend(long_options_map[opt][0])
if opt.startswith('-') and not opt.startswith('--'):
options.extend(opt[1:])
@ -499,6 +519,8 @@ def main():
unbuffer = 'u' in options
version_info = 'v' in options
yaml_out = 'y' in options
bash_comp = 'B' in options
zsh_comp = 'Z' in options
if verbose_debug:
tracebackplus.enable(context=11)
@ -523,6 +545,14 @@ def main():
utils._safe_print(versiontext())
sys.exit(0)
if bash_comp:
utils._safe_print(bash_completion())
sys.exit(0)
if zsh_comp:
utils._safe_print(zsh_completion())
sys.exit(0)
# if magic syntax used, try to run the command and error if it's not found, etc.
magic_stdout, magic_stderr, magic_exit_code = None, None, 0
if run_command:

18
jc/cli_data.py Normal file
View File

@ -0,0 +1,18 @@
"""jc - JSON Convert cli_data module"""
from typing import List, Dict
long_options_map: Dict[str, List[str]] = {
'--about': ['a', 'about jc'],
'--force-color': ['C', 'force color output even when using pipes (overrides -m)'],
'--debug': ['d', 'debug (double for verbose debug)'],
'--help': ['h', 'help (--help --parser_name for parser documentation)'],
'--monochrome': ['m', 'monochrome output'],
'--pretty': ['p', 'pretty print output'],
'--quiet': ['q', 'suppress warnings (double to ignore streaming errors)'],
'--raw': ['r', 'raw output'],
'--unbuffer': ['u', 'unbuffer output'],
'--version': ['v', 'version info'],
'--yaml-out': ['y', 'YAML output'],
'--bash-comp': ['B', 'gen Bash completion: jc -B > /etc/bash_completion.d/jc'],
'--zsh-comp': ['Z', 'gen Zsh completion: jc -Z > "${fpath[1]}/_jc"']
}

View File

@ -6,7 +6,7 @@ import importlib
from typing import Dict, List, Iterable, Union, Iterator
from jc import appdirs
__version__ = '1.20.0'
__version__ = '1.20.1'
parsers = [
'acpi',
@ -73,6 +73,7 @@ parsers = [
'ping-s',
'pip-list',
'pip-show',
'postconf',
'ps',
'route',
'rpm-qi',

View File

@ -54,6 +54,9 @@ etc...
Headers (keys) are converted to snake-case. All values are returned as
strings, except empty strings, which are converted to None/null.
> Note: To preserve the case of the keys use the `-r` cli option or
> `raw=True` argument in `parse()`.
Usage (cli):
$ cat table.txt | jc --asciitable
@ -122,7 +125,7 @@ from jc.parsers.universal import sparse_table_parse
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.1'
version = '1.2'
description = 'ASCII and Unicode table parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@ -144,6 +147,12 @@ def _process(proc_data: List[Dict]) -> List[Dict]:
List of Dictionaries. Structured to conform to the schema.
"""
# normalize keys: convert to lowercase
for item in proc_data:
for key in item.copy():
k_new = key.lower()
item[k_new] = item.pop(key)
return proc_data
@ -227,12 +236,11 @@ def _is_separator(line: str) -> bool:
def _snake_case(line: str) -> str:
"""
Replace spaces between words and special characters with an underscore
and set to lowercase. Ignore the replacement char (�) used for header
padding.
Replace spaces between words and special characters with an underscore.
Ignore the replacement char (�) used for header padding.
"""
line = re.sub(r'[^a-zA-Z0-9� ]', '_', line) # special characters
line = re.sub(r'\b \b', '_', line).lower() # spaces betwee words
line = re.sub(r'\b \b', '_', line) # spaces between words
return line

View File

@ -24,6 +24,9 @@ Headers (keys) are converted to snake-case and newlines between multi-line
headers are joined with an underscore. All values are returned as strings,
except empty strings, which are converted to None/null.
> Note: To preserve the case of the keys use the `-r` cli option or
> `raw=True` argument in `parse()`.
> Note: table column separator characters (e.g. `|`) cannot be present
> inside the cell data. If detected, a warning message will be printed to
> `STDERR` and the line will be skipped. The warning message can be
@ -107,7 +110,7 @@ from jc.exceptions import ParseError
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.1'
version = '1.2'
description = 'multi-line ASCII and Unicode table parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@ -129,6 +132,12 @@ def _process(proc_data: List[Dict]) -> List[Dict]:
List of Dictionaries. Structured to conform to the schema.
"""
# normalize keys: convert to lowercase
for item in proc_data:
for key in item.copy():
k_new = key.lower()
item[k_new] = item.pop(key)
return proc_data
@ -233,12 +242,11 @@ def _is_separator(line: str) -> bool:
def _snake_case(line: str) -> str:
"""
replace spaces between words and special characters with an underscore
and set to lowercase
replace spaces between words and special characters with an underscore.
"""
# must include all column separator characters in regex
line = re.sub(r'[^a-zA-Z0-9 |│┃┆┇┊┋╎╏║]', '_', line)
return re.sub(r'\b \b', '_', line).lower()
return re.sub(r'\b \b', '_', line)
def _fixup_separators(line: str) -> str:

View File

@ -105,7 +105,7 @@ import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.4'
version = '1.5'
description = '`id` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@ -144,6 +144,13 @@ def _process(proc_data):
return proc_data
def _get_item(my_list, index, default=None):
if index < len(my_list):
return my_list[index]
return default
def parse(data, raw=False, quiet=False):
"""
Main text parsing function
@ -174,14 +181,14 @@ def parse(data, raw=False, quiet=False):
uid_parsed = uid_parsed.split('=')
raw_output['uid'] = {}
raw_output['uid']['id'] = uid_parsed[1]
raw_output['uid']['name'] = uid_parsed[2]
raw_output['uid']['name'] = _get_item(uid_parsed, 2)
if section.startswith('gid'):
gid_parsed = section.replace('(', '=').replace(')', '=')
gid_parsed = gid_parsed.split('=')
raw_output['gid'] = {}
raw_output['gid']['id'] = gid_parsed[1]
raw_output['gid']['name'] = gid_parsed[2]
raw_output['gid']['name'] = _get_item(gid_parsed, 2)
if section.startswith('groups'):
groups_parsed = section.replace('(', '=').replace(')', '=')
@ -193,7 +200,7 @@ def parse(data, raw=False, quiet=False):
group_dict = {}
grp_parsed = group.split('=')
group_dict['id'] = grp_parsed[0]
group_dict['name'] = grp_parsed[1]
group_dict['name'] = _get_item(grp_parsed, 1)
raw_output['groups'].append(group_dict)
if section.startswith('context'):

View File

@ -6,9 +6,10 @@ Parses standard `INI` files and files containing simple key/value pairs.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If duplicate keys are found, only the last value will be used.
> Note: Values starting and ending with quotation marks will have the marks
> removed. If you would like to keep the quotation marks, use the `-r`
> command-line argument or the `raw=True` argument in `parse()`.
> Note: Values starting and ending with double or single quotation marks
> will have the marks removed. If you would like to keep the quotation
> marks, use the `-r` command-line argument or the `raw=True` argument in
> `parse()`.
Usage (cli):
@ -69,7 +70,7 @@ import configparser
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.6'
version = '1.7'
description = 'INI file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@ -99,17 +100,21 @@ def _process(proc_data):
for key, value in proc_data[heading].items():
if value is not None and value.startswith('"') and value.endswith('"'):
proc_data[heading][key] = value.lstrip('"').rstrip('"')
elif value is not None and value.startswith("'") and value.endswith("'"):
proc_data[heading][key] = value.lstrip("'").rstrip("'")
elif value is None:
proc_data[heading][key] = ''
# simple key/value files with no headers
else:
if (proc_data[heading] is not None and
proc_data[heading].startswith('"') and
proc_data[heading].endswith('"')):
if proc_data[heading] is not None and proc_data[heading].startswith('"') and proc_data[heading].endswith('"'):
proc_data[heading] = proc_data[heading].lstrip('"').rstrip('"')
elif proc_data[heading] is not None and proc_data[heading].startswith("'") and proc_data[heading].endswith("'"):
proc_data[heading] = proc_data[heading].lstrip("'").rstrip("'")
elif proc_data[heading] is None:
proc_data[heading] = ''

173
jc/parsers/postconf.py Normal file
View File

@ -0,0 +1,173 @@
"""jc - JSON Convert `postconf -M` command output parser
Usage (cli):
$ postconf -M | jc --postconf
or
$ jc postconf -M
Usage (module):
import jc
result = jc.parse('postconf', postconf_command_output)
Schema:
[
{
"service_name": string,
"service_type": string,
"private": boolean/null, # [0]
"unprivileged": boolean/null, # [0]
"chroot": boolean/null, # [0]
"wake_up_time": integer/null, # [0]
"no_wake_up_before_first_use": boolean/null, # [1]
"process_limit": integer/null, # [0]
"command": string
}
]
[0] '-' converted to null/None
[1] null/None if `wake_up_time` is null/None
Examples:
$ postconf -M | jc --postconf -p
[
{
"service_name": "smtp",
"service_type": "inet",
"private": false,
"unprivileged": null,
"chroot": true,
"wake_up_time": null,
"process_limit": null,
"command": "smtpd",
"no_wake_up_before_first_use": null
},
{
"service_name": "pickup",
"service_type": "unix",
"private": false,
"unprivileged": null,
"chroot": true,
"wake_up_time": 60,
"process_limit": 1,
"command": "pickup",
"no_wake_up_before_first_use": false
}
]
$ postconf -M | jc --postconf -p -r
[
{
"service_name": "smtp",
"service_type": "inet",
"private": "n",
"unprivileged": "-",
"chroot": "y",
"wake_up_time": "-",
"process_limit": "-",
"command": "smtpd"
},
{
"service_name": "pickup",
"service_type": "unix",
"private": "n",
"unprivileged": "-",
"chroot": "y",
"wake_up_time": "60",
"process_limit": "1",
"command": "pickup"
}
]
"""
from typing import List, Dict
import jc.utils
from jc.parsers.universal import simple_table_parse
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`postconf -M` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux']
magic_commands = ['postconf -M']
__version__ = info.version
def _process(proc_data: List[Dict]) -> List[Dict]:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (List of Dictionaries) raw structured data to process
Returns:
List of Dictionaries. Structured to conform to the schema.
"""
keys = ['private', 'unprivileged', 'chroot', 'wake_up_time', 'process_limit']
bools = ['private', 'unprivileged', 'chroot']
integers = ['wake_up_time', 'process_limit']
for item in proc_data:
if item['wake_up_time'].endswith('?'):
item['no_wake_up_before_first_use'] = True
elif item['wake_up_time'] == '-':
item['no_wake_up_before_first_use'] = None
else:
item['no_wake_up_before_first_use'] = False
for key in keys:
if item[key] == '-':
item[key] = None
for key in bools:
if item[key] is not None:
item[key] = jc.utils.convert_to_bool(item[key])
for key in integers:
if item[key] is not None:
item[key] = jc.utils.convert_to_int(item[key])
return proc_data
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> List[Dict]:
"""
Main text parsing function
Parameters:
data: (string) text data to parse
raw: (boolean) unprocessed output if True
quiet: (boolean) suppress warning messages if True
Returns:
List of Dictionaries. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: List = []
if jc.utils.has_data(data):
table = ['service_name service_type private unprivileged chroot wake_up_time process_limit command']
data_list = list(filter(None, data.splitlines()))
table.extend(data_list)
raw_output = simple_table_parse(table)
return raw_output if raw else _process(raw_output)

348
jc/shell_completions.py Normal file
View File

@ -0,0 +1,348 @@
"""jc - JSON Convert shell_completions module"""
from string import Template
from .cli_data import long_options_map
from .lib import all_parser_info
bash_template = Template('''\
_jc()
{
local cur prev words cword jc_commands jc_parsers jc_options \\
jc_about_options jc_about_mod_options jc_help_options jc_special_options
jc_commands=(${bash_commands})
jc_parsers=(${bash_parsers})
jc_options=(${bash_options})
jc_about_options=(${bash_about_options})
jc_about_mod_options=(${bash_about_mod_options})
jc_help_options=(${bash_help_options})
jc_special_options=(${bash_special_options})
COMPREPLY=()
_get_comp_words_by_ref cur prev words cword
# if jc_about_options are found anywhere in the line, then only complete from jc_about_mod_options
for i in "$${words[@]::$${#words[@]}-1}"; do
if [[ " $${jc_about_options[*]} " =~ " $${i} " ]]; then
COMPREPLY=( $$( compgen -W "$${jc_about_mod_options[*]}" \\
-- "$${cur}" ) )
return 0
fi
done
# if jc_help_options and a parser are found anywhere in the line, then no more completions
if
(
for i in "$${words[@]::$${#words[@]}-1}"; do
if [[ " $${jc_help_options[*]} " =~ " $${i} " ]]; then
return 0
fi
done
return 1
) && (
for i in "$${words[@]::$${#words[@]}-1}"; do
if [[ " $${jc_parsers[*]} " =~ " $${i} " ]]; then
return 0
fi
done
return 1
); then
return 0
fi
# if jc_help_options are found anywhere in the line, then only complete with parsers
for i in "$${words[@]::$${#words[@]}-1}"; do
if [[ " $${jc_help_options[*]} " =~ " $${i} " ]]; then
COMPREPLY=( $$( compgen -W "$${jc_parsers[*]}" \\
-- "$${cur}" ) )
return 0
fi
done
# if special options are found anywhere in the line, then no more completions
for i in "$${words[@]::$${#words[@]}-1}"; do
if [[ " $${jc_special_options[*]} " =~ " $${i} " ]]; then
return 0
fi
done
# if magic command is found anywhere in the line, use called command's autocompletion
for i in "$${words[@]::$${#words[@]}-1}"; do
if [[ " $${jc_commands[*]} " =~ " $${i} " ]]; then
_command
return 0
fi
done
# if a parser arg is found anywhere in the line, only show options and help options
for i in "$${words[@]::$${#words[@]}-1}"; do
if [[ " $${jc_parsers[*]} " =~ " $${i} " ]]; then
COMPREPLY=( $$( compgen -W "$${jc_options[*]} $${jc_help_options[*]}" \\
-- "$${cur}" ) )
return 0
fi
done
# default completion
COMPREPLY=( $$( compgen -W "$${jc_options[*]} $${jc_about_options[*]} $${jc_help_options[*]} $${jc_special_options[*]} $${jc_parsers[*]} $${jc_commands[*]}" \\
-- "$${cur}" ) )
} &&
complete -F _jc jc
''')
zsh_template = Template('''\
#compdef jc
_jc() {
local -a jc_commands jc_commands_describe \\
jc_parsers jc_parsers_describe \\
jc_options jc_options_describe \\
jc_about_options jc_about_options_describe \\
jc_about_mod_options jc_about_mod_options_describe \\
jc_help_options jc_help_options_describe \\
jc_special_options jc_special_options_describe
jc_commands=(${zsh_commands})
jc_commands_describe=(
${zsh_commands_describe}
)
jc_parsers=(${zsh_parsers})
jc_parsers_describe=(
${zsh_parsers_describe}
)
jc_options=(${zsh_options})
jc_options_describe=(
${zsh_options_describe}
)
jc_about_options=(${zsh_about_options})
jc_about_options_describe=(
${zsh_about_options_describe}
)
jc_about_mod_options=(${zsh_about_mod_options})
jc_about_mod_options_describe=(
${zsh_about_mod_options_describe}
)
jc_help_options=(${zsh_help_options})
jc_help_options_describe=(
${zsh_help_options_describe}
)
jc_special_options=(${zsh_special_options})
jc_special_options_describe=(
${zsh_special_options_describe}
)
# if jc_about_options are found anywhere in the line, then only complete from jc_about_mod_options
for i in $${words:0:-1}; do
if (( $$jc_about_options[(Ie)$${i}] )); then
_describe 'commands' jc_about_mod_options_describe
return 0
fi
done
# if jc_help_options and a parser are found anywhere in the line, then no more completions
if
(
for i in $${words:0:-1}; do
if (( $$jc_help_options[(Ie)$${i}] )); then
return 0
fi
done
return 1
) && (
for i in $${words:0:-1}; do
if (( $$jc_parsers[(Ie)$${i}] )); then
return 0
fi
done
return 1
); then
return 0
fi
# if jc_help_options are found anywhere in the line, then only complete with parsers
for i in $${words:0:-1}; do
if (( $$jc_help_options[(Ie)$${i}] )); then
_describe 'commands' jc_parsers_describe
return 0
fi
done
# if special options are found anywhere in the line, then no more completions
for i in $${words:0:-1}; do
if (( $$jc_special_options[(Ie)$${i}] )); then
return 0
fi
done
# if magic command is found anywhere in the line, use called command's autocompletion
for i in $${words:0:-1}; do
if (( $$jc_commands[(Ie)$${i}] )); then
# hack to remove options between jc and the magic command
shift $$(( $${#words} - 2 )) words
words[1,0]=(jc)
CURRENT=$${#words}
# run the magic command's completions
_arguments '*::arguments:_normal'
return 0
fi
done
# if a parser arg is found anywhere in the line, only show options and help options
for i in $${words:0:-1}; do
if (( $$jc_parsers[(Ie)$${i}] )); then
_describe 'commands' jc_options_describe -- jc_help_options_describe
return 0
fi
done
# default completion
_describe 'commands' jc_options_describe -- jc_about_options_describe -- jc_help_options_describe -- jc_special_options_describe -- jc_parsers_describe -- jc_commands_describe
}
_jc
''')
about_options = ['--about', '-a']
about_mod_options = ['--pretty', '-p', '--yaml-out', '-y', '--monochrome', '-m', '--force-color', '-C']
help_options = ['--help', '-h']
special_options = ['--version', '-v', '--bash-comp', '-B', '--zsh-comp', '-Z']
def get_commands():
command_list = []
for cmd in all_parser_info():
if 'magic_commands' in cmd:
command_list.extend(cmd['magic_commands'])
return sorted(list(set([i.split()[0] for i in command_list])))
def get_options():
options_list = []
for opt in long_options_map:
options_list.append(opt)
options_list.append('-' + long_options_map[opt][0])
return options_list
def get_parsers():
p_list = []
for cmd in all_parser_info():
if 'argument' in cmd:
p_list.append(cmd['argument'])
return p_list
def get_parsers_descriptions():
pd_list = []
for p in all_parser_info():
if 'description' in p:
pd_list.append(f"'{p['argument']}:{p['description']}'")
return pd_list
def get_zsh_command_descriptions(command_list):
zsh_commands = []
for cmd in command_list:
zsh_commands.append(f"""'{cmd}:run "{cmd}" command with magic syntax.'""")
return zsh_commands
def get_descriptions(opt_list):
"""Return a list of options:description items."""
opt_desc_list = []
for item in opt_list:
# get long options
if item in long_options_map:
opt_desc_list.append(f"'{item}:{long_options_map[item][1]}'")
continue
# get short options
for k, v in long_options_map.items():
if item[1:] == v[0]:
opt_desc_list.append(f"'{item}:{v[1]}'")
continue
return opt_desc_list
def bash_completion():
parsers_str = ' '.join(get_parsers())
opts_no_special = get_options()
for s_option in special_options:
opts_no_special.remove(s_option)
for a_option in about_options:
opts_no_special.remove(a_option)
for h_option in help_options:
opts_no_special.remove(h_option)
options_str = ' '.join(opts_no_special)
about_options_str = ' '.join(about_options)
about_mod_options_str = ' '.join(about_mod_options)
help_options_str = ' '.join(help_options)
special_options_str = ' '.join(special_options)
commands_str = ' '.join(get_commands())
return bash_template.substitute(
bash_parsers=parsers_str,
bash_special_options=special_options_str,
bash_about_options=about_options_str,
bash_about_mod_options=about_mod_options_str,
bash_help_options=help_options_str,
bash_options=options_str,
bash_commands=commands_str
)
def zsh_completion():
parsers_str = ' '.join(get_parsers())
parsers_describe = '\n '.join(get_parsers_descriptions())
opts_no_special = get_options()
for s_option in special_options:
opts_no_special.remove(s_option)
for a_option in about_options:
opts_no_special.remove(a_option)
for h_option in help_options:
opts_no_special.remove(h_option)
options_str = ' '.join(opts_no_special)
options_describe = '\n '.join(get_descriptions(opts_no_special))
about_options_str = ' '.join(about_options)
about_options_describe = '\n '.join(get_descriptions(about_options))
about_mod_options_str = ' '.join(about_mod_options)
about_mod_options_describe = '\n '.join(get_descriptions(about_mod_options))
help_options_str = ' '.join(help_options)
help_options_describe = '\n '.join(get_descriptions(help_options))
special_options_str = ' '.join(special_options)
special_options_describe = '\n '.join(get_descriptions(special_options))
commands_str = ' '.join(get_commands())
commands_describe = '\n '.join(get_zsh_command_descriptions(get_commands()))
return zsh_template.substitute(
zsh_parsers=parsers_str,
zsh_parsers_describe=parsers_describe,
zsh_special_options=special_options_str,
zsh_special_options_describe=special_options_describe,
zsh_about_options=about_options_str,
zsh_about_options_describe=about_options_describe,
zsh_about_mod_options=about_mod_options_str,
zsh_about_mod_options_describe=about_mod_options_describe,
zsh_help_options=help_options_str,
zsh_help_options_describe=help_options_describe,
zsh_options=options_str,
zsh_options_describe=options_describe,
zsh_commands=commands_str,
zsh_commands_describe=commands_describe
)

View File

@ -1,4 +1,4 @@
.TH jc 1 2022-05-31 1.20.0 "JSON Convert"
.TH jc 1 2022-06-15 1.20.1 "JSON Convert"
.SH NAME
\fBjc\fP \- JSON Convert JSONifies the output of many CLI tools and file-types
.SH SYNOPSIS
@ -337,6 +337,11 @@ Key/Value file parser
\fB--pip-show\fP
`pip show` command parser
.TP
.B
\fB--postconf\fP
`postconf -M` command parser
.TP
.B
\fB--ps\fP
@ -536,48 +541,56 @@ Options:
.TP
.B
\fB-a\fP
about \fBjc\fP (JSON or YAML output)
\fB-a\fP, \fB--about\fP
About \fBjc\fP (JSON or YAML output)
.TP
.B
\fB-C\fP
force color output even when using pipes (overrides \fB-m\fP and the \fBNO_COLOR\fP env variable)
\fB-C\fP, \fB--force-color\fP
Force color output even when using pipes (overrides \fB-m\fP and the \fBNO_COLOR\fP env variable)
.TP
.B
\fB-d\fP
debug - show traceback (use \fB-dd\fP for verbose traceback)
\fB-d\fP, \fB--debug\fP
Debug - show traceback (use \fB-dd\fP for verbose traceback)
.TP
.B
\fB-h\fP
help (\fB-h --parser_name\fP for parser documentation)
\fB-h\fP, \fB--help\fP
Help (\fB--help --parser_name\fP for parser documentation)
.TP
.B
\fB-m\fP
monochrome output
\fB-m\fP, \fB--monochrome\fP
Monochrome output
.TP
.B
\fB-p\fP
pretty print output
\fB-p\fP, \fB--pretty\fP
Pretty print output
.TP
.B
\fB-q\fP
quiet - suppress warnings (use \fB-qq\fP to ignore streaming parser errors)
\fB-q\fP, \fB--quiet\fP
Quiet mode. Suppresses parser warning messages (use -qq to ignore streaming parser errors)
.TP
.B
\fB-r\fP
raw JSON output
\fB-r\fP, \fB--raw\fP
Raw output. Provides more literal output, typically with string values and no additional semantic processing
.TP
.B
\fB-u\fP
unbuffer output (useful for slow streaming data with streaming parsers)
\fB-u\fP, \fB--unbuffer\fP
Unbuffer output (useful for slow streaming data with streaming parsers)
.TP
.B
\fB-v\fP
version information
\fB-v\fP, \fB--version\fP
Version information
.TP
.B
\fB-y\fP
\fB-y\fP, \fB--yaml-out\fP
YAML output
.TP
.B
\fB-B\fP, \fB--bash-comp\fP
Generate Bash shell completion script
.TP
.B
\fB-Z\fP, \fB--zsh-comp\fP
Generate Zsh shell completion script
.SH EXIT CODES
Any fatal errors within \fBjc\fP will generate an exit code of \fB100\fP, otherwise the exit code will be \fB0\fP. When using the "Magic" syntax (e.g. \fBjc ifconfig eth0\fP), \fBjc\fP will store the exit code of the program being parsed and add it to the \fBjc\fP exit code. This way it is easier to determine if an error was from the parsed program or \fBjc\fP.

View File

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

View File

@ -31,48 +31,56 @@ Options:
.TP
.B
\fB-a\fP
about \fBjc\fP (JSON or YAML output)
\fB-a\fP, \fB--about\fP
About \fBjc\fP (JSON or YAML output)
.TP
.B
\fB-C\fP
force color output even when using pipes (overrides \fB-m\fP and the \fBNO_COLOR\fP env variable)
\fB-C\fP, \fB--force-color\fP
Force color output even when using pipes (overrides \fB-m\fP and the \fBNO_COLOR\fP env variable)
.TP
.B
\fB-d\fP
debug - show traceback (use \fB-dd\fP for verbose traceback)
\fB-d\fP, \fB--debug\fP
Debug - show traceback (use \fB-dd\fP for verbose traceback)
.TP
.B
\fB-h\fP
help (\fB-h --parser_name\fP for parser documentation)
\fB-h\fP, \fB--help\fP
Help (\fB--help --parser_name\fP for parser documentation)
.TP
.B
\fB-m\fP
monochrome output
\fB-m\fP, \fB--monochrome\fP
Monochrome output
.TP
.B
\fB-p\fP
pretty print output
\fB-p\fP, \fB--pretty\fP
Pretty print output
.TP
.B
\fB-q\fP
quiet - suppress warnings (use \fB-qq\fP to ignore streaming parser errors)
\fB-q\fP, \fB--quiet\fP
Quiet mode. Suppresses parser warning messages (use -qq to ignore streaming parser errors)
.TP
.B
\fB-r\fP
raw JSON output
\fB-r\fP, \fB--raw\fP
Raw output. Provides more literal output, typically with string values and no additional semantic processing
.TP
.B
\fB-u\fP
unbuffer output (useful for slow streaming data with streaming parsers)
\fB-u\fP, \fB--unbuffer\fP
Unbuffer output (useful for slow streaming data with streaming parsers)
.TP
.B
\fB-v\fP
version information
\fB-v\fP, \fB--version\fP
Version information
.TP
.B
\fB-y\fP
\fB-y\fP, \fB--yaml-out\fP
YAML output
.TP
.B
\fB-B\fP, \fB--bash-comp\fP
Generate Bash shell completion script
.TP
.B
\fB-Z\fP, \fB--zsh-comp\fP
Generate Zsh shell completion script
.SH EXIT CODES
Any fatal errors within \fBjc\fP will generate an exit code of \fB100\fP, otherwise the exit code will be \fB0\fP. When using the "Magic" syntax (e.g. \fBjc ifconfig eth0\fP), \fBjc\fP will store the exit code of the program being parsed and add it to the \fBjc\fP exit code. This way it is easier to determine if an error was from the parsed program or \fBjc\fP.

View File

@ -149,22 +149,21 @@ option.
| `{{ "{:>15}".format(parser.argument) }}` | {{ "{:<55}".format(parser.description) }} | {{ "{:<70}".format("[📃](https://kellyjonbrazil.github.io/jc/docs/parsers/" + parser.name + ")") }} |{% endfor %}
### Options
- `-a` about `jc`. Prints information about `jc` and the parsers (in JSON or
YAML, of course!)
- `-C` force color output even when using pipes (overrides `-m` and the
`NO_COLOR` env variable)
- `-d` debug mode. Prints trace messages if parsing issues are encountered (use
`-dd` for verbose debugging)
- `-h` help. Use `jc -h --parser_name` for parser documentation
- `-m` monochrome JSON output
- `-p` pretty format the JSON output
- `-q` quiet mode. Suppresses parser warning messages (use `-qq` to ignore
streaming parser errors)
- `-r` raw output. Provides a more literal JSON output, typically with string
values and no additional semantic processing
- `-u` unbuffer output
- `-v` version information
- `-y` YAML output
| Short | Long | Description |
|-------|-----------------|--------------------------------------------------------------------------------------------------------------|
| `-a` | `--about` | About `jc`. Prints information about `jc` and the parsers (in JSON or YAML, of course!) |
| `-C` | `--force-color` | Force color output even when using pipes (overrides `-m` and the `NO_COLOR` env variable) |
| `-d` | `--debug` | Debug mode. Prints trace messages if parsing issues are encountered (use`-dd` for verbose debugging) |
| `-h` | `--help` | Help. Use `jc -h --parser_name` for parser documentation |
| `-m` | `--monochrome` | Monochrome output |
| `-p` | `--pretty` | Pretty format the JSON output |
| `-q` | `--quiet` | Quiet mode. Suppresses parser warning messages (use `-qq` to ignore streaming parser errors) |
| `-r` | `--raw` | Raw output. Provides more literal output, typically with string values and no additional semantic processing |
| `-u` | `--unbuffer` | Unbuffer output |
| `-v` | `--version` | Version information |
| `-y` | `--yaml-out` | YAML output |
| `-B` | `--bash-comp` | Generate Bash shell completion script |
| `-Z` | `--zsh-comp` | Generate Zsh shell completion script |
### Exit Codes
Any fatal errors within `jc` will generate an exit code of `100`, otherwise the

View File

@ -0,0 +1,4 @@
[client]
user=foo
host=localhost
password="bar"

View File

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

View File

@ -0,0 +1,4 @@
[client]
user=foo
host=localhost
password='bar'

View File

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

File diff suppressed because one or more lines are too long

31
tests/fixtures/generic/postconf-M.out vendored Normal file
View File

@ -0,0 +1,31 @@
smtp inet n - y - - smtpd
pickup unix n - y 60 1 pickup
cleanup unix n - y - 0 cleanup
qmgr unix n - n 300 1 qmgr
tlsmgr unix - - y 1000? 1 tlsmgr
rewrite unix - - y - - trivial-rewrite
bounce unix - - y - 0 bounce
defer unix - - y - 0 bounce
trace unix - - y - 0 bounce
verify unix - - y - 1 verify
flush unix n - y 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - y - - smtp
relay unix - - y - - smtp -o syslog_name=postfix/$service_name
showq unix n - y - - showq
error unix - - y - - error
retry unix - - y - - error
discard unix - - y - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - y - - lmtp
anvil unix - - y - 1 anvil
scache unix - - y - 1 scache
postlog unix-dgram n - n - 1 postlogd
maildrop unix - n n - - pipe flags=DRXhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
uucp unix - n n - - pipe flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail unix - n n - - pipe flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp unix - n n - - pipe flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix - n n - 2 pipe flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman unix - n n - - pipe flags=FRX user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py ${nexthop} ${user}

View File

@ -344,6 +344,49 @@ Internet 10.12.13.4 198 0950.5C8A.5c41 ARPA GigabitEthernet2.17
self.assertEqual(jc.parsers.asciitable.parse(input, quiet=True), expected)
def test_asciitable_no_lower_raw(self):
"""
Test 'asciitable' with a pure ASCII table that has special
characters and mixed case in the header. These should be converted to underscores
and no trailing or consecutive underscores should end up in the
resulting key names. Using `raw` in this test to preserve case. (no lower)
"""
input = '''
Protocol Address Age (min) Hardware Addr Type Interface
Internet 10.12.13.1 98 0950.5785.5cd1 ARPA FastEthernet2.13
Internet 10.12.13.3 131 0150.7685.14d5 ARPA GigabitEthernet2.13
Internet 10.12.13.4 198 0950.5C8A.5c41 ARPA GigabitEthernet2.17
'''
expected = [
{
"Protocol": "Internet",
"Address": "10.12.13.1",
"Age_min": "98",
"Hardware_Addr": "0950.5785.5cd1",
"Type": "ARPA",
"Interface": "FastEthernet2.13"
},
{
"Protocol": "Internet",
"Address": "10.12.13.3",
"Age_min": "131",
"Hardware_Addr": "0150.7685.14d5",
"Type": "ARPA",
"Interface": "GigabitEthernet2.13"
},
{
"Protocol": "Internet",
"Address": "10.12.13.4",
"Age_min": "198",
"Hardware_Addr": "0950.5C8A.5c41",
"Type": "ARPA",
"Interface": "GigabitEthernet2.17"
}
]
self.assertEqual(jc.parsers.asciitable.parse(input, raw=True, quiet=True), expected)
def test_asciitable_centered_col_header(self):
"""
Test 'asciitable' with long centered column header which can break

View File

@ -270,6 +270,34 @@ class MyTests(unittest.TestCase):
self.assertEqual(jc.parsers.asciitable_m.parse(input, quiet=True), expected)
def test_asciitable_no_lower_raw(self):
"""
Test 'asciitable_m' with a pure ASCII table that has special
characters and mixed case in the header. These should be converted to underscores
and no trailing or consecutive underscores should end up in the
resulting key names. Using `raw` in this test to preserve case. (no lower)
"""
input = '''
+----------+------------+-----------+----------------+-------+--------------------+
| Protocol | Address | Age (min) | Hardware Addr | Type | Interface |
| | | of int | | | |
+----------+------------+-----------+----------------+-------+--------------------+
| Internet | 10.12.13.1 | 98 | 0950.5785.5cd1 | ARPA | FastEthernet2.13 |
+----------+------------+-----------+----------------+-------+--------------------+
'''
expected = [
{
"Protocol": "Internet",
"Address": "10.12.13.1",
"Age_min_of_int": "98",
"Hardware_Addr": "0950.5785.5cd1",
"Type": "ARPA",
"Interface": "FastEthernet2.13"
}
]
self.assertEqual(jc.parsers.asciitable_m.parse(input, raw=True, quiet=True), expected)
def test_asciitable_m_sep_char_in_cell(self):
"""
Test 'asciitable_m' with a column separator character inside the data

View File

@ -25,7 +25,10 @@ class MyTests(unittest.TestCase):
'jc -h': (False, None, None, []),
'jc -h --arp': (False, None, None, []),
'jc -h arp': (False, None, None, []),
'jc -h arp -a': (False, None, None, [])
'jc -h arp -a': (False, None, None, []),
'jc --pretty dig': (True, ['dig'], '--dig', ['p']),
'jc --pretty --monochrome --quiet --raw dig': (True, ['dig'], '--dig', ['p', 'm', 'q', 'r']),
'jc --about --yaml-out': (False, None, None, [])
}
for command, expected_command in commands.items():

View File

@ -29,6 +29,20 @@ class MyTests(unittest.TestCase):
"""
self.assertEqual(jc.parsers.id.parse('', quiet=True), {})
def test_id_no_name(self):
"""
Test 'id' with no name
"""
self.assertEqual(
jc.parsers.id.parse('uid=1000 gid=1000 groups=1000,10', quiet=True),
{'uid': {'id': 1000, 'name': None}, 'gid': {'id': 1000, 'name': None}, 'groups': [{'id': 1000, 'name': None}, {'id': 10, 'name': None}]}
)
self.assertEqual(
jc.parsers.id.parse('uid=1000(user) gid=1000 groups=1000,10(wheel)', quiet=True),
{'uid': {'id': 1000, 'name': 'user'}, 'gid': {'id': 1000, 'name': None}, 'groups': [{'id': 1000, 'name': None}, {'id': 10, 'name': 'wheel'}]}
)
def test_id_centos_7_7(self):
"""
Test 'id' on Centos 7.7

View File

@ -16,6 +16,12 @@ class MyTests(unittest.TestCase):
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-iptelserver.ini'), 'r', encoding='utf-8') as f:
self.generic_ini_iptelserver = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-double-quote.ini'), 'r', encoding='utf-8') as f:
self.generic_ini_double_quote = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-single-quote.ini'), 'r', encoding='utf-8') as f:
self.generic_ini_single_quote = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-test.json'), 'r', encoding='utf-8') as f:
self.generic_ini_test_json = json.loads(f.read())
@ -23,6 +29,12 @@ class MyTests(unittest.TestCase):
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-iptelserver.json'), 'r', encoding='utf-8') as f:
self.generic_ini_iptelserver_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-double-quote.json'), 'r', encoding='utf-8') as f:
self.generic_ini_double_quote_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/ini-single-quote.json'), 'r', encoding='utf-8') as f:
self.generic_ini_single_quote_json = json.loads(f.read())
def test_ini_nodata(self):
"""
Test the test ini file with no data
@ -53,6 +65,19 @@ duplicate_key = value2
expected = {'duplicate_key': 'value2', 'another_key': 'foo'}
self.assertEqual(jc.parsers.ini.parse(data, quiet=True), expected)
def test_ini_doublequote(self):
"""
Test ini file with double quotes around a value
"""
self.assertEqual(jc.parsers.ini.parse(self.generic_ini_double_quote, quiet=True), self.generic_ini_double_quote_json)
def test_ini_singlequote(self):
"""
Test ini file with single quotes around a value
"""
self.assertEqual(jc.parsers.ini.parse(self.generic_ini_single_quote, quiet=True), self.generic_ini_single_quote_json)
if __name__ == '__main__':
unittest.main()

35
tests/test_postconf.py Normal file
View File

@ -0,0 +1,35 @@
import os
import unittest
import json
import jc.parsers.postconf
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
class MyTests(unittest.TestCase):
def setUp(self):
# input
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/postconf-M.out'), 'r', encoding='utf-8') as f:
self.generic_postconf_m = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/postconf-M.json'), 'r', encoding='utf-8') as f:
self.generic_postconf_m_json = json.loads(f.read())
def test_postconf_nodata(self):
"""
Test 'postconf' with no data
"""
self.assertEqual(jc.parsers.postconf.parse('', quiet=True), [])
def test_postconf(self):
"""
Test 'postconf -M'
"""
self.assertEqual(jc.parsers.postconf.parse(self.generic_postconf_m, quiet=True), self.generic_postconf_m_json)
if __name__ == '__main__':
unittest.main()