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

Merge pull request #234 from kellyjonbrazil/dev

Dev v1.18.7
This commit is contained in:
Kelly Brazil
2022-04-25 17:17:49 -07:00
committed by GitHub
73 changed files with 298205 additions and 127 deletions

View File

@ -1,5 +1,17 @@
jc changelog
20220425 v1.18.7
- Add git log command parser
- Add update-alternatives --query parser
- Add update-alternatives --get-selections parser
- Fix key/value and ini parsers to allow duplicate keys
- Fix yaml file parser for files including timestamp objects
- Fix UnicodeDecodeError on some systems where LANG=C is set and unicode
characters are in the output
- Update xrandr parser: add a 'rotation' field
- Fix failing tests by moving template files
- Add python interpreter version and path to -v and -a output
20220325 v1.18.6
- Add pidstat command parser tested on linux
- Add pidstat command streaming parser tested on linux
@ -361,16 +373,16 @@ jc changelog
- Add axfr support for dig command parser
20200312 v1.9.2
- Updated arp parser to fix OSX detection for some edge cases
- Update arp parser to fix OSX detection for some edge cases
20200312 v1.9.1
- Updated file command parser to make filename splitting more robust
- Update file command parser to make filename splitting more robust
20200311 v1.9.0
- Added ntpq command parser
- Added timedatectl status command parser
- Added airport -I and airport -s command parser
- Added file command parser
- Add ntpq command parser
- Add timedatectl status command parser
- Add airport -I and airport -s command parser
- Add file command parser
- Optimized history command parser by https://github.com/philippeitis
- Magic syntax fix for certain edge cases
@ -378,23 +390,23 @@ jc changelog
- CLI optimizations by https://github.com/philippeitis
- Refactored magic syntax function and added tests (https://github.com/philippeitis)
- Github actions for CI testing on multiple platforms by https://github.com/philippeitis
- Updated ls parser to fix parsing error in OSX with -lR when there are empty folders
- Update ls parser to fix parsing error in OSX with -lR when there are empty folders
20200303 v1.8.0
- Added blkid command parser
- Added last and lastb command parser
- Added who command parser
- Added CSV file parser
- Added /etc/passwd file parser
- Added /etc/shadow file parser
- Added /etc/group file parser
- Added /etc/gshadow file parser
- Add blkid command parser
- Add last and lastb command parser
- Add who command parser
- Add CSV file parser
- Add /etc/passwd file parser
- Add /etc/shadow file parser
- Add /etc/group file parser
- Add /etc/gshadow file parser
20200227 v1.7.5
- Updated ls parser to support filenames with newline characters
- Update ls parser to support filenames with newline characters
20200219 v1.7.4
- Updated ls parser to support multiple directories, globbing, and -R (recursive)
- Update ls parser to support multiple directories, globbing, and -R (recursive)
20200211 v1.7.3
- Add alternative 'magic' syntax: e.g. `jc ls -al`
@ -411,8 +423,8 @@ jc changelog
- Add crontab file parser with user support (tested on linux)
- Add __version__ variable to parser modules
- Add exit code on error
- Updated history parser to output "line" as an integer
- Updated compatibility list for some parsers
- Update history parser to output "line" as an integer
- Update compatibility list for some parsers
- Bugfix in crontab file parser: header insertion was clobbering first row
- Just-in-time loading of parser modules instead of loading all at start
@ -425,7 +437,7 @@ jc changelog
- Add tests for ls, dig, ps, w, uptime on OSX
- Add about option
- Add universal parsers to refactor repetitive code
- Updated ifconfig parser to output 'state' as an array
- Update ifconfig parser to output 'state' as an array
20191117 v1.5.1
- Add ss parser
@ -438,11 +450,11 @@ jc changelog
- Add -d option to debug parsing issues
- Add compatibility warnings to stderr
- Add documentation
- Updated iptables parser to allow --line-numbers option
- Updated lsblk parser to allow parsing of added columns
- Updated mount parser: changed 'access' field name to 'options'
- Updated netstat parser to allow parsing of unix sockets and raw network connections
- Updated w parser to fix unaligned data where blanks are possible
- Update iptables parser to allow --line-numbers option
- Update lsblk parser to allow parsing of added columns
- Update mount parser: changed 'access' field name to 'options'
- Update netstat parser to allow parsing of unix sockets and raw network connections
- Update w parser to fix unaligned data where blanks are possible
- Clean up code and reorganize package
20191031 v1.1.1

View File

@ -3645,6 +3645,69 @@ uname -a | jc --uname -p # or: jc -p uname -a
"kernel_version": "#74-Ubuntu SMP Tue Sep 17 17:06:04 UTC 2019"
}
```
### update-alternatives --get-selections
```bash
update-alternatives --get-selections | jc --update-alt-gs -p # or: jc -p update-alternatives --get-selections
```
```json
[
{
"name": "arptables",
"status": "auto",
"current": "/usr/sbin/arptables-nft"
},
{
"name": "awk",
"status": "auto",
"current": "/usr/bin/gawk"
}
]
```
### update-alternatives --query
```bash
update-alternatives --query editor | jc --update-alt-q -p # or: jc -p update-alternatives --query editor
```
```json
{
"name": "editor",
"link": "/usr/bin/editor",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/editor.1.gz"
},
{
"name": "editor.da.1.gz",
"path": "/usr/share/man/da/man1/editor.1.gz"
}
],
"status": "auto",
"best": "/bin/nano",
"value": "/bin/nano",
"alternatives": [
{
"name": "/bin/ed",
"priority": -100,
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/ed.1.gz"
}
]
},
{
"name": "/bin/nano",
"priority": 40,
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/nano.1.gz"
}
]
}
]
}
```
### upower
```bash
upower -i /org/freedesktop/UPower/devices/battery | jc --upower -p # or jc -p upower -i /org/freedesktop/UPower/devices/battery

View File

@ -107,7 +107,7 @@ pip3 install jc
### OS Package Repositories
| OS | Command |
|-----------------------|-------------------------------------------------------------------------------|
|--------------------------------------|-------------------------------------------------------------------------------|
| Debian/Ubuntu linux | `apt-get install jc` |
| Fedora linux | `dnf install jc` |
| openSUSE linux | `zypper install jc` |
@ -167,6 +167,7 @@ option.
- `--finger` enables the `finger` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/finger))
- `--free` enables the `free` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/free))
- `--fstab` enables the `/etc/fstab` file parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/fstab))
- `--git-log` enables the `git log` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/git_log))
- `--group` enables the `/etc/group` file parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/group))
- `--gshadow` enables the `/etc/gshadow` file parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/gshadow))
- `--hash` enables the `hash` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/hash))
@ -227,6 +228,8 @@ option.
- `--ufw` enables the `ufw status` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw))
- `--ufw-appinfo` enables the `ufw app info [application]` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/ufw_appinfo))
- `--uname` enables the `uname -a` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/uname))
- `--update-alt-gs` enables the `update-alternatives --get-selections` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/update_alt_gs))
- `--update-alt-q` enables the `update-alternatives --query` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/update_alt_q))
- `--upower` enables the `upower` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/upower))
- `--uptime` enables the `uptime` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/uptime))
- `--vmstat` enables the `vmstat` command parser ([documentation](https://kellyjonbrazil.github.io/jc/docs/parsers/vmstat))
@ -399,9 +402,9 @@ Local parser plugins are standard python module files. Use the
or [`jc/parsers/foo_s.py (streaming)`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo_s.py)
parser as a template and simply place a `.py` file in the `jcparsers` subfolder.
Local plugin filenames must be valid python module names, therefore must consist
entirely of alphanumerics and start with a letter. Local plugins may override
default parsers.
Local plugin filenames must be valid python module names and therefore must
start with a letter and consist entirely of alphanumerics. Local plugins
may override default parsers.
> Note: The application data directory follows the
[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)

View File

@ -76,20 +76,30 @@ EOF
)
cd jc
echo Building docs for: package
pydoc-markdown -m jc "${readme_config}" > ../docs/readme.md
(
echo Building docs for: package
pydoc-markdown -m jc "${readme_config}" > ../docs/readme.md; echo "+++ package docs complete"
) &
echo Building docs for: lib
pydoc-markdown -m jc.lib "${toc_config}" > ../docs/lib.md
(
echo Building docs for: lib
pydoc-markdown -m jc.lib "${toc_config}" > ../docs/lib.md; echo "+++ lib docs complete"
) &
echo Building docs for: utils
pydoc-markdown -m jc.utils "${toc_config}" > ../docs/utils.md
(
echo Building docs for: utils
pydoc-markdown -m jc.utils "${toc_config}" > ../docs/utils.md; echo "+++ utils docs complete"
) &
echo Building docs for: streaming
pydoc-markdown -m jc.streaming "${toc_config}" > ../docs/streaming.md
(
echo Building docs for: streaming
pydoc-markdown -m jc.streaming "${toc_config}" > ../docs/streaming.md; echo "+++ streaming docs complete"
) &
echo Building docs for: universal parser
pydoc-markdown -m jc.parsers.universal "${toc_config}" > ../docs/parsers/universal.md
(
echo Building docs for: universal parser
pydoc-markdown -m jc.parsers.universal "${toc_config}" > ../docs/parsers/universal.md; echo "+++ universal parser docs complete"
) &
# a bit of inception here... jc is being used to help
# automate the generation of its own documentation. :)
@ -103,7 +113,7 @@ do
done < <(jc -a | jq -c '.parsers[] | select(.plugin != true)')
for parser in "${parsers[@]}"
do
do (
parser_name=$(jq -r '.name' <<< "$parser")
compatible=$(jq -r '.compatible | join(", ")' <<< "$parser")
version=$(jq -r '.version' <<< "$parser")
@ -117,4 +127,8 @@ do
echo "Compatibility: ${compatible}" >> ../docs/parsers/"${parser_name}".md
echo >> ../docs/parsers/"${parser_name}".md
echo "Version ${version} by ${author} (${author_email})" >> ../docs/parsers/"${parser_name}".md
echo "+++ ${parser_name} docs complete"
) &
done
wait
echo "Document Generation Complete"

View File

@ -68,7 +68,7 @@ Parameters:
variants of the module name.
data: (string or data to parse (string for normal
iterator) parsers, iterator of strings for
iterable) parsers, iterable of strings for
streaming parsers)
raw: (boolean) output preprocessed JSON if True

175
docs/parsers/git_log.md Normal file
View File

@ -0,0 +1,175 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.git_log"></a>
# jc.parsers.git\_log
jc - JSON Convert `git log` command output parser
Can be used with the following format options:
- `oneline`
- `short`
- `medium`
- `full`
- `fuller`
Additional options supported:
- `--stat`
- `--shortstat`
The `epoch` calculated timestamp field is naive. (i.e. based on the
local time of the system the parser is run on)
The `epoch_utc` calculated timestamp field is timezone-aware and is
only available if the timezone field is UTC.
Usage (cli):
$ git log | jc --git-log
or
$ jc git log
Usage (module):
import jc
result = jc.parse('git_log', git_log_command_output)
Schema:
[
{
"commit": string,
"author": string,
"author_email": string,
"date": string,
"epoch": integer, [0]
"epoch_utc": integer, [1]
"commit_by": string,
"commit_by_email": string,
"commit_by_date": string,
"message": string,
"stats" : {
"files_changed": integer,
"insertions": integer,
"deletions": integer,
"files": [
string
]
}
}
]
[0] naive timestamp if "date" field is parsable, else null
[1] timezone aware timestamp availabe for UTC, else null
Examples:
$ git log --stat | jc --git-log -p
[
{
"commit": "728d882ed007b3c8b785018874a0eb06e1143b66",
"author": "Kelly Brazil",
"author_email": "kellyjonbrazil@gmail.com",
"date": "Wed Apr 20 09:50:19 2022 -0400",
"stats": {
"files_changed": 2,
"insertions": 90,
"deletions": 12,
"files": [
"docs/parsers/git_log.md",
"jc/parsers/git_log.py"
]
},
"message": "add timestamp docs and examples",
"epoch": 1650462619,
"epoch_utc": null
},
{
"commit": "b53e42aca623181aa9bc72194e6eeef1e9a3a237",
"author": "Kelly Brazil",
"author_email": "kellyjonbrazil@gmail.com",
"date": "Wed Apr 20 09:44:42 2022 -0400",
"stats": {
"files_changed": 5,
"insertions": 29,
"deletions": 6,
"files": [
"docs/parsers/git_log.md",
"docs/utils.md",
"jc/parsers/git_log.py",
"jc/utils.py",
"man/jc.1"
]
},
"message": "add calculated timestamp",
"epoch": 1650462282,
"epoch_utc": null
},
...
]
$ git log --stat | jc --git-log -p -r
[
{
"commit": "728d882ed007b3c8b785018874a0eb06e1143b66",
"author": "Kelly Brazil",
"author_email": "kellyjonbrazil@gmail.com",
"date": "Wed Apr 20 09:50:19 2022 -0400",
"stats": {
"files_changed": "2",
"insertions": "90",
"deletions": "12",
"files": [
"docs/parsers/git_log.md",
"jc/parsers/git_log.py"
]
},
"message": "add timestamp docs and examples"
},
{
"commit": "b53e42aca623181aa9bc72194e6eeef1e9a3a237",
"author": "Kelly Brazil",
"author_email": "kellyjonbrazil@gmail.com",
"date": "Wed Apr 20 09:44:42 2022 -0400",
"stats": {
"files_changed": "5",
"insertions": "29",
"deletions": "6",
"files": [
"docs/parsers/git_log.md",
"docs/utils.md",
"jc/parsers/git_log.py",
"jc/utils.py",
"man/jc.1"
]
},
"message": "add calculated timestamp"
},
...
]
<a id="jc.parsers.git_log.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, darwin, cygwin, win32, aix, freebsd
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@ -6,12 +6,14 @@
jc - JSON Convert `INI` file parser
Parses standard `INI` files and files containing simple key/value pairs.
Delimiter can be `=` or `:`. Missing values are supported. Comment prefix
can be `#` or `;`. Comments must be on their own line.
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()`.
- Delimiter can be `=` or `:`. Missing values are supported.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If duplicate keys are found, only the last value will be used.
> 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()`.
Usage (cli):
@ -89,4 +91,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, win32, aix, freebsd
Version 1.5 by Kelly Brazil (kellyjonbrazil@gmail.com)
Version 1.6 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@ -5,13 +5,15 @@
jc - JSON Convert `Key/Value` file parser
Supports files containing simple key/value pairs. Delimiter can be `=` or
`:`. Missing values are supported. Comment prefix can be `#` or `;`.
Comments must be on their own line.
Supports files containing simple key/value pairs.
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()`.
- Delimiter can be `=` or `:`. Missing values are supported.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If duplicate keys are found, only the last value will be used.
> 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()`.
Usage (cli):
@ -78,4 +80,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

@ -0,0 +1,71 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.update_alt_gs"></a>
# jc.parsers.update\_alt\_gs
jc - JSON Convert `update-alternatives --get-selections` command output parser
Usage (cli):
$ update-alternatives --get-selections | jc --update-alt-gs
or
$ jc update-alternatives --get-selections
Usage (module):
import jc
result = jc.parse('update-alt-gs',
update_alternatives_get_selections_command_output)
Schema:
[
{
"name": string,
"status": string,
"current": string
}
]
Examples:
$ update-alternatives --get-selections | jc --update-alt-gs -p
[
{
"name": "arptables",
"status": "auto",
"current": "/usr/sbin/arptables-nft"
},
{
"name": "awk",
"status": "auto",
"current": "/usr/bin/gawk"
}
]
<a id="jc.parsers.update_alt_gs.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

@ -0,0 +1,157 @@
[Home](https://kellyjonbrazil.github.io/jc/)
<a id="jc.parsers.update_alt_q"></a>
# jc.parsers.update\_alt\_q
jc - JSON Convert `update-alternatives --query` command output parser
Usage (cli):
$ update-alternatives --query | jc --update-alt-q
or
$ jc update-alternatives --query
Usage (module):
import jc
result = jc.parse('update_alt_q',
update_alternatives_query_command_output)
Schema:
{
"name": string,
"link": string,
"slaves": [
{
"name": string,
"path": string
}
],
"status": string,
"best": string,
"value": string, # (null if 'none')
"alternatives": [
{
"alternative": string,
"priority": integer,
"slaves": [
{
"name": string,
"path": string
}
]
}
]
}
Examples:
$ update-alternatives --query editor | jc --update-alt-q -p
{
"name": "editor",
"link": "/usr/bin/editor",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/editor.1.gz"
},
{
"name": "editor.da.1.gz",
"path": "/usr/share/man/da/man1/editor.1.gz"
}
],
"status": "auto",
"best": "/bin/nano",
"value": "/bin/nano",
"alternatives": [
{
"alternative": "/bin/ed",
"priority": -100,
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/ed.1.gz"
}
]
},
{
"alternative": "/bin/nano",
"priority": 40,
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/nano.1.gz"
}
]
}
]
}
$ update-alternatives --query | jc --update-alt-q -p -r
{
"name": "editor",
"link": "/usr/bin/editor",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/editor.1.gz"
},
{
"name": "editor.da.1.gz",
"path": "/usr/share/man/da/man1/editor.1.gz"
}
],
"status": "auto",
"best": "/bin/nano",
"value": "/bin/nano",
"alternatives": [
{
"alternative": "/bin/ed",
"priority": "-100",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/ed.1.gz"
}
]
},
{
"alternative": "/bin/nano",
"priority": "40",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/nano.1.gz"
}
]
}
]
}
<a id="jc.parsers.update_alt_q.parse"></a>
### parse
```python
def parse(data: str, raw: bool = False, quiet: bool = False) -> 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:
Dictionary. Raw or processed structured data.
### Parser Information
Compatibility: linux
Version 1.0 by Kelly Brazil (kellyjonbrazil@gmail.com)

View File

@ -54,7 +54,8 @@ Schema:
"offset_width": integer,
"offset_height": integer,
"dimension_width": integer,
"dimension_height": integer
"dimension_height": integer,
"rotation": string
}
],
"unassociated_devices": [
@ -130,7 +131,8 @@ Examples:
"offset_width": 0,
"offset_height": 0,
"dimension_width": 310,
"dimension_height": 170
"dimension_height": 170,
"rotation": "normal"
}
}
],
@ -160,4 +162,4 @@ Returns:
### Parser Information
Compatibility: linux, darwin, cygwin, aix, freebsd
Version 1.0 by Kevin Lyter (lyter_git at sent.com)
Version 1.1 by Kevin Lyter (lyter_git at sent.com)

View File

@ -5,6 +5,8 @@
jc - JSON Convert `YAML` file parser
Note: datetime objects will be converted to strings.
Usage (cli):
$ cat foo.yaml | jc --yaml
@ -107,4 +109,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)

View File

@ -187,7 +187,7 @@ class timestamp()
```python
def __init__(datetime_string: str,
format_hint: Union[List, Tuple, None] = None) -> None
format_hint: Optional[Iterable] = None) -> None
```
Input a datetime text string of several formats and convert to a
@ -198,7 +198,7 @@ Parameters:
datetime_string (str): a string representation of a
datetime in several supported formats
format_hint (list | tuple): an optional list of format ID
format_hint (iterable): an optional iterable of format ID
integers to instruct the timestamp object to try those
formats first in the order given. Other formats will be
tried after the format hint list is exhausted. This can

View File

@ -37,7 +37,7 @@ class info():
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
website = 'https://github.com/kellyjonbrazil/jc'
copyright = '© 2019-2022 Kelly Brazil'
copyright = f'© 2019-2022 Kelly Brazil'
license = 'MIT License'
@ -84,6 +84,16 @@ if PYGMENTS_INSTALLED:
}
def asciify(string):
"""
Return a string downgraded from Unicode to ASCII with some simple
conversions.
"""
string = string.replace('©', '(c)')
string = ascii(string)
return string.replace(r'\n', '\n')
def set_env_colors(env_colors=None):
"""
Return a dictionary to be used in Pygments custom style class.
@ -177,6 +187,8 @@ def about_jc():
'website': info.website,
'copyright': info.copyright,
'license': info.license,
'python_version': '.'.join((str(sys.version_info.major), str(sys.version_info.minor), str(sys.version_info.micro))),
'python_path': sys.executable,
'parser_count': len(all_parser_info()),
'parsers': all_parser_info()
}
@ -249,8 +261,12 @@ def help_doc(options):
def versiontext():
"""Return the version text"""
py_ver = '.'.join((str(sys.version_info.major), str(sys.version_info.minor), str(sys.version_info.micro)))
versiontext_string = f'''\
jc version {info.version}
jc version: {info.version}
python interpreter version: {py_ver}
python path: {sys.executable}
{info.website}
{info.copyright}'''
return textwrap.dedent(versiontext_string)
@ -273,10 +289,23 @@ def json_out(data, pretty=False, env_colors=None, mono=False, piped_out=False):
class JcStyle(Style):
styles = set_env_colors(env_colors)
return str(highlight(json.dumps(data, indent=indent, separators=separators, ensure_ascii=False),
try:
return str(highlight(json.dumps(data,
indent=indent,
separators=separators,
ensure_ascii=False),
JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
except UnicodeEncodeError:
return str(highlight(json.dumps(data,
indent=indent,
separators=separators,
ensure_ascii=True),
JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
try:
return json.dumps(data, indent=indent, separators=separators, ensure_ascii=False)
except UnicodeEncodeError:
return json.dumps(data, indent=indent, separators=separators, ensure_ascii=True)
def magic_parser(args):
@ -422,11 +451,17 @@ def main():
sys.exit(0)
if help_me:
try:
print(help_doc(sys.argv))
except UnicodeEncodeError:
print(asciify(help_doc(sys.argv)))
sys.exit(0)
if version_info:
try:
print(versiontext())
except UnicodeEncodeError:
print(asciify(versiontext()))
sys.exit(0)
# if magic syntax used, try to run the command and error if it's not found, etc.

View File

@ -6,7 +6,7 @@ import importlib
from typing import Dict, List, Iterable, Union, Iterator
from jc import appdirs
__version__ = '1.18.6'
__version__ = '1.18.7'
parsers = [
'acpi',
@ -33,6 +33,7 @@ parsers = [
'finger',
'free',
'fstab',
'git-log',
'group',
'gshadow',
'hash',
@ -93,6 +94,8 @@ parsers = [
'ufw',
'ufw-appinfo',
'uname',
'update-alt-gs',
'update-alt-q',
'upower',
'uptime',
'vmstat',
@ -206,7 +209,7 @@ def parse(
variants of the module name.
data: (string or data to parse (string for normal
iterator) parsers, iterator of strings for
iterable) parsers, iterable of strings for
streaming parsers)
raw: (boolean) output preprocessed JSON if True

329
jc/parsers/git_log.py Normal file
View File

@ -0,0 +1,329 @@
"""jc - JSON Convert `git log` command output parser
Can be used with the following format options:
- `oneline`
- `short`
- `medium`
- `full`
- `fuller`
Additional options supported:
- `--stat`
- `--shortstat`
The `epoch` calculated timestamp field is naive. (i.e. based on the
local time of the system the parser is run on)
The `epoch_utc` calculated timestamp field is timezone-aware and is
only available if the timezone field is UTC.
Usage (cli):
$ git log | jc --git-log
or
$ jc git log
Usage (module):
import jc
result = jc.parse('git_log', git_log_command_output)
Schema:
[
{
"commit": string,
"author": string,
"author_email": string,
"date": string,
"epoch": integer, [0]
"epoch_utc": integer, [1]
"commit_by": string,
"commit_by_email": string,
"commit_by_date": string,
"message": string,
"stats" : {
"files_changed": integer,
"insertions": integer,
"deletions": integer,
"files": [
string
]
}
}
]
[0] naive timestamp if "date" field is parsable, else null
[1] timezone aware timestamp availabe for UTC, else null
Examples:
$ git log --stat | jc --git-log -p
[
{
"commit": "728d882ed007b3c8b785018874a0eb06e1143b66",
"author": "Kelly Brazil",
"author_email": "kellyjonbrazil@gmail.com",
"date": "Wed Apr 20 09:50:19 2022 -0400",
"stats": {
"files_changed": 2,
"insertions": 90,
"deletions": 12,
"files": [
"docs/parsers/git_log.md",
"jc/parsers/git_log.py"
]
},
"message": "add timestamp docs and examples",
"epoch": 1650462619,
"epoch_utc": null
},
{
"commit": "b53e42aca623181aa9bc72194e6eeef1e9a3a237",
"author": "Kelly Brazil",
"author_email": "kellyjonbrazil@gmail.com",
"date": "Wed Apr 20 09:44:42 2022 -0400",
"stats": {
"files_changed": 5,
"insertions": 29,
"deletions": 6,
"files": [
"docs/parsers/git_log.md",
"docs/utils.md",
"jc/parsers/git_log.py",
"jc/utils.py",
"man/jc.1"
]
},
"message": "add calculated timestamp",
"epoch": 1650462282,
"epoch_utc": null
},
...
]
$ git log --stat | jc --git-log -p -r
[
{
"commit": "728d882ed007b3c8b785018874a0eb06e1143b66",
"author": "Kelly Brazil",
"author_email": "kellyjonbrazil@gmail.com",
"date": "Wed Apr 20 09:50:19 2022 -0400",
"stats": {
"files_changed": "2",
"insertions": "90",
"deletions": "12",
"files": [
"docs/parsers/git_log.md",
"jc/parsers/git_log.py"
]
},
"message": "add timestamp docs and examples"
},
{
"commit": "b53e42aca623181aa9bc72194e6eeef1e9a3a237",
"author": "Kelly Brazil",
"author_email": "kellyjonbrazil@gmail.com",
"date": "Wed Apr 20 09:44:42 2022 -0400",
"stats": {
"files_changed": "5",
"insertions": "29",
"deletions": "6",
"files": [
"docs/parsers/git_log.md",
"docs/utils.md",
"jc/parsers/git_log.py",
"jc/utils.py",
"man/jc.1"
]
},
"message": "add calculated timestamp"
},
...
]
"""
import re
from typing import List, Dict
import jc.utils
hash_pattern = re.compile(r'([0-9]|[a-f])+')
changes_pattern = re.compile(r'\s(?P<files>\d+)\s+(files? changed),\s+(?P<insertions>\d+)\s(insertions?\(\+\))?(,\s+)?(?P<deletions>\d+)?(\s+deletions?\(\-\))?')
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`git log` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux', 'darwin', 'cygwin', 'win32', 'aix', 'freebsd']
magic_commands = ['git log']
__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.
"""
int_list = ['files_changed', 'insertions', 'deletions']
for entry in proc_data:
if 'date' in entry:
ts = jc.utils.timestamp(entry['date'], format_hint=(1100,))
entry['epoch'] = ts.naive
entry['epoch_utc'] = ts.utc
if 'stats' in entry:
for key in entry['stats']:
if key in int_list:
entry['stats'][key] = jc.utils.convert_to_int(entry['stats'][key])
return proc_data
def _is_commit_hash(hash_string: str) -> bool:
# 0c55240e9da30ac4293dc324f1094de2abd3da91
if len(hash_string) != 40:
return False
if hash_pattern.match(hash_string):
return True
return False
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 = []
output_line: Dict = {}
message_lines: List[str] = []
file_list: List[str] = []
if jc.utils.has_data(data):
for line in data.splitlines():
line_list = line.split(maxsplit=1)
# oneline style
if line_list and _is_commit_hash(line_list[0]):
if output_line:
if file_list:
output_line['stats']['files'] = file_list
raw_output.append(output_line)
output_line = {}
message_lines = []
file_list = []
output_line = {
'commit': line_list[0],
'message': line_list[1]
}
continue
# all other styles
if line.startswith('commit '):
if output_line:
if message_lines:
output_line['message'] = '\n'.join(message_lines)
if file_list:
output_line['stats']['files'] = file_list
raw_output.append(output_line)
output_line = {}
message_lines = []
file_list = []
output_line['commit'] = line_list[1]
continue
if line.startswith('Merge: '):
output_line['merge'] = line_list[1]
continue
if line.startswith('Author: '):
values = line_list[1].rsplit(maxsplit=1)
output_line['author'] = values[0]
output_line['author_email'] = values[1].strip('<').strip('>')
continue
if line.startswith('Date: '):
output_line['date'] = line_list[1]
continue
if line.startswith('AuthorDate: '):
output_line['date'] = line_list[1]
continue
if line.startswith('CommitDate: '):
output_line['commit_by_date'] = line_list[1]
continue
if line.startswith('Commit: '):
values = line_list[1].rsplit(maxsplit=1)
output_line['commit_by'] = values[0]
output_line['commit_by_email'] = values[1].strip('<').strip('>')
continue
if line.startswith(' '):
message_lines.append(line.strip())
continue
if line.startswith(' ') and 'changed, ' not in line:
# this is a file name
file_name = line.split('|')[0].strip()
file_list.append(file_name)
continue
if line.startswith(' ') and 'changed, ' in line:
# this is the stat summary
changes = changes_pattern.match(line)
if changes:
files = changes['files']
insertions = changes['insertions']
deletions = changes['deletions']
output_line['stats'] = {
'files_changed': files or '0',
'insertions': insertions or '0',
'deletions': deletions or '0'
}
if output_line:
if message_lines:
output_line['message'] = '\n'.join(message_lines)
if file_list:
output_line['stats']['files'] = file_list
raw_output.append(output_line)
return raw_output if raw else _process(raw_output)

View File

@ -1,12 +1,14 @@
"""jc - JSON Convert `INI` file parser
Parses standard `INI` files and files containing simple key/value pairs.
Delimiter can be `=` or `:`. Missing values are supported. Comment prefix
can be `#` or `;`. Comments must be on their own line.
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()`.
- Delimiter can be `=` or `:`. Missing values are supported.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If duplicate keys are found, only the last value will be used.
> 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()`.
Usage (cli):
@ -67,7 +69,7 @@ import configparser
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.5'
version = '1.6'
description = 'INI file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@ -135,7 +137,9 @@ def parse(data, raw=False, quiet=False):
if jc.utils.has_data(data):
ini = configparser.ConfigParser(allow_no_value=True, interpolation=None)
ini = configparser.ConfigParser(allow_no_value=True,
interpolation=None,
strict=False)
try:
ini.read_string(data)
raw_output = {s: dict(ini.items(s)) for s in ini.sections()}

View File

@ -1,12 +1,14 @@
"""jc - JSON Convert `Key/Value` file parser
Supports files containing simple key/value pairs. Delimiter can be `=` or
`:`. Missing values are supported. Comment prefix can be `#` or `;`.
Comments must be on their own line.
Supports files containing simple key/value pairs.
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()`.
- Delimiter can be `=` or `:`. Missing values are supported.
- Comment prefix can be `#` or `;`. Comments must be on their own line.
- If duplicate keys are found, only the last value will be used.
> 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()`.
Usage (cli):
@ -52,7 +54,7 @@ Examples:
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.1'
version = '1.2'
description = 'Key/Value file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'

111
jc/parsers/update_alt_gs.py Normal file
View File

@ -0,0 +1,111 @@
"""jc - JSON Convert `update-alternatives --get-selections` command output parser
Usage (cli):
$ update-alternatives --get-selections | jc --update-alt-gs
or
$ jc update-alternatives --get-selections
Usage (module):
import jc
result = jc.parse('update-alt-gs',
update_alternatives_get_selections_command_output)
Schema:
[
{
"name": string,
"status": string,
"current": string
}
]
Examples:
$ update-alternatives --get-selections | jc --update-alt-gs -p
[
{
"name": "arptables",
"status": "auto",
"current": "/usr/sbin/arptables-nft"
},
{
"name": "awk",
"status": "auto",
"current": "/usr/bin/gawk"
}
]
"""
from typing import List, Dict
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`update-alternatives --get-selections` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux']
magic_commands = ['update-alternatives --get-selections']
__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.
"""
# nothing to process
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 = []
output_line = {}
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
line_list = line.split(maxsplit=2)
output_line = {
"name": line_list[0],
"status": line_list[1],
"current": line_list[2]
}
raw_output.append(output_line)
return raw_output if raw else _process(raw_output)

261
jc/parsers/update_alt_q.py Normal file
View File

@ -0,0 +1,261 @@
"""jc - JSON Convert `update-alternatives --query` command output parser
Usage (cli):
$ update-alternatives --query | jc --update-alt-q
or
$ jc update-alternatives --query
Usage (module):
import jc
result = jc.parse('update_alt_q',
update_alternatives_query_command_output)
Schema:
{
"name": string,
"link": string,
"slaves": [
{
"name": string,
"path": string
}
],
"status": string,
"best": string,
"value": string, # (null if 'none')
"alternatives": [
{
"alternative": string,
"priority": integer,
"slaves": [
{
"name": string,
"path": string
}
]
}
]
}
Examples:
$ update-alternatives --query editor | jc --update-alt-q -p
{
"name": "editor",
"link": "/usr/bin/editor",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/editor.1.gz"
},
{
"name": "editor.da.1.gz",
"path": "/usr/share/man/da/man1/editor.1.gz"
}
],
"status": "auto",
"best": "/bin/nano",
"value": "/bin/nano",
"alternatives": [
{
"alternative": "/bin/ed",
"priority": -100,
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/ed.1.gz"
}
]
},
{
"alternative": "/bin/nano",
"priority": 40,
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/nano.1.gz"
}
]
}
]
}
$ update-alternatives --query | jc --update-alt-q -p -r
{
"name": "editor",
"link": "/usr/bin/editor",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/editor.1.gz"
},
{
"name": "editor.da.1.gz",
"path": "/usr/share/man/da/man1/editor.1.gz"
}
],
"status": "auto",
"best": "/bin/nano",
"value": "/bin/nano",
"alternatives": [
{
"alternative": "/bin/ed",
"priority": "-100",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/ed.1.gz"
}
]
},
{
"alternative": "/bin/nano",
"priority": "40",
"slaves": [
{
"name": "editor.1.gz",
"path": "/usr/share/man/man1/nano.1.gz"
}
]
}
]
}
"""
from typing import List, Dict
import jc.utils
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.0'
description = '`update-alternatives --query` command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
compatible = ['linux']
magic_commands = ['update-alternatives --query']
__version__ = info.version
def _process(proc_data: Dict) -> Dict:
"""
Final processing to conform to the schema.
Parameters:
proc_data: (Dictionary) raw structured data to process
Returns:
Dictionary. Structured to conform to the schema.
"""
int_list = ['priority']
if 'value' in proc_data:
if proc_data['value'] == 'none':
proc_data['value'] = None
if 'alternatives' in proc_data:
for index, alt in enumerate(proc_data['alternatives']):
for key in alt:
if key in int_list:
proc_data['alternatives'][index][key] = jc.utils.convert_to_int(proc_data['alternatives'][index][key])
return proc_data
def parse(
data: str,
raw: bool = False,
quiet: bool = False
) -> 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:
Dictionary. Raw or processed structured data.
"""
jc.utils.compatibility(__name__, info.compatible, quiet)
jc.utils.input_type_check(data)
raw_output: Dict = {}
slaves: List = []
alt_obj: Dict = {}
if jc.utils.has_data(data):
for line in filter(None, data.splitlines()):
line_list = line.split(maxsplit=1)
if line.startswith('Name: '):
raw_output['name'] = line_list[1]
continue
if line.startswith('Link: '):
raw_output['link'] = line_list[1]
continue
if line.startswith('Slaves:'):
continue
if line.startswith(' '):
s_name = line_list[0].strip()
s_path = line_list[1]
slaves.append(
{
"name": s_name,
"path": s_path
}
)
continue
if line.startswith('Status: '):
if slaves:
raw_output['slaves'] = slaves
slaves = []
raw_output['status'] = line_list[1]
continue
if line.startswith('Best: '):
raw_output['best'] = line_list[1]
continue
if line.startswith('Value: '):
raw_output['value'] = line_list[1]
continue
if line.startswith('Alternative: '):
if not 'alternatives' in raw_output:
raw_output['alternatives'] = []
if slaves:
alt_obj['slaves'] = slaves
raw_output['alternatives'].append(alt_obj)
slaves = []
alt_obj = {"alternative": line_list[1]}
continue
if line.startswith('Priority: '):
alt_obj['priority'] = line_list[1]
continue
if alt_obj:
if slaves:
alt_obj['slaves'] = slaves
raw_output['alternatives'].append(alt_obj)
return raw_output if raw else _process(raw_output)

View File

@ -49,7 +49,8 @@ Schema:
"offset_width": integer,
"offset_height": integer,
"dimension_width": integer,
"dimension_height": integer
"dimension_height": integer,
"rotation": string
}
],
"unassociated_devices": [
@ -125,7 +126,8 @@ Examples:
"offset_width": 0,
"offset_height": 0,
"dimension_width": 310,
"dimension_height": 170
"dimension_height": 170,
"rotation": "normal"
}
}
],
@ -140,7 +142,7 @@ import jc.utils
class info:
"""Provides parser metadata (version, author, etc.)"""
version = "1.0"
version = "1.1"
description = "`xrandr` command parser"
author = "Kevin Lyter"
author_email = "lyter_git at sent.com"
@ -252,6 +254,7 @@ _device_pattern = (
+ "(?P<is_primary> primary)? ?"
+ "((?P<resolution_width>\d+)x(?P<resolution_height>\d+)"
+ "\+(?P<offset_width>\d+)\+(?P<offset_height>\d+))? "
+ "(?P<rotation>(inverted|left|right))? ?"
+ "\(normal left inverted right x axis y axis\)"
+ "( ((?P<dimension_width>\d+)mm x (?P<dimension_height>\d+)mm)?)?"
)
@ -275,9 +278,10 @@ def _parse_device(next_lines: List[str], quiet: bool = False) -> Optional[Device
"is_primary": matches["is_primary"] is not None
and len(matches["is_primary"]) > 0,
"device_name": matches["device_name"],
"rotation": matches["rotation"] or "normal",
}
for k, v in matches.items():
if k not in {"is_connected", "is_primary", "device_name"}:
if k not in {"is_connected", "is_primary", "device_name", "rotation"}:
try:
if v:
device[k] = int(v)

View File

@ -1,5 +1,7 @@
"""jc - JSON Convert `YAML` file parser
Note: datetime objects will be converted to strings.
Usage (cli):
$ cat foo.yaml | jc --yaml
@ -85,7 +87,7 @@ from jc.exceptions import LibraryNotInstalled
class info():
"""Provides parser metadata (version, author, etc.)"""
version = '1.6'
version = '1.7'
description = 'YAML file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@ -147,6 +149,11 @@ def parse(data, raw=False, quiet=False):
yaml = YAML(typ='safe')
# modify the timestamp constructor to output datetime objects as
# strings since JSON does not support datetime objects
yaml.constructor.yaml_constructors['tag:yaml.org,2002:timestamp'] = \
yaml.constructor.yaml_constructors['tag:yaml.org,2002:str']
for document in yaml.load_all(data):
raw_output.append(document)

View File

@ -6,7 +6,7 @@ import shutil
from datetime import datetime, timezone
from textwrap import TextWrapper
from functools import lru_cache
from typing import List, Tuple, Union, Optional
from typing import List, Iterable, Union, Optional
def warning_message(message_lines: List[str]) -> None:
@ -233,7 +233,7 @@ def input_type_check(data: str) -> None:
class timestamp:
def __init__(self,
datetime_string: str,
format_hint: Union[List, Tuple, None] = None
format_hint: Optional[Iterable] = None
) -> None:
"""
Input a datetime text string of several formats and convert to a
@ -244,7 +244,7 @@ class timestamp:
datetime_string (str): a string representation of a
datetime in several supported formats
format_hint (list | tuple): an optional list of format ID
format_hint (iterable): an optional iterable of format ID
integers to instruct the timestamp object to try those
formats first in the order given. Other formats will be
tried after the format hint list is exhausted. This can
@ -361,6 +361,7 @@ class timestamp:
formats = [
{'id': 1000, 'format': '%a %b %d %H:%M:%S %Y', 'locale': None}, # manual C locale format conversion: Tue Mar 23 16:12:11 2021 or Tue Mar 23 16:12:11 IST 2021
{'id': 1100, 'format': '%a %b %d %H:%M:%S %Y %z', 'locale': None}, # git date output: Thu Mar 5 09:17:40 2020 -0800
{'id': 1500, 'format': '%Y-%m-%d %H:%M', 'locale': None}, # en_US.UTF-8 local format (found in who cli output): 2021-03-23 00:14
{'id': 1600, 'format': '%m/%d/%Y %I:%M %p', 'locale': None}, # Windows english format (found in dir cli output): 12/07/2019 02:09 AM
{'id': 1700, 'format': '%m/%d/%Y, %I:%M:%S %p', 'locale': None}, # Windows english format wint non-UTC tz (found in systeminfo cli output): 3/22/2021, 1:15:51 PM (UTC-0600)

View File

@ -1,4 +1,4 @@
.TH jc 1 2022-03-25 1.18.6 "JSON Convert"
.TH jc 1 2022-04-25 1.18.7 "JSON Convert"
.SH NAME
jc \- JSONifies the output of many CLI tools and file-types
.SH SYNOPSIS
@ -137,6 +137,11 @@ CSV file streaming parser
\fB--fstab\fP
`/etc/fstab` file parser
.TP
.B
\fB--git-log\fP
`git log` command parser
.TP
.B
\fB--group\fP
@ -437,6 +442,16 @@ Key/Value file parser
\fB--uname\fP
`uname -a` command parser
.TP
.B
\fB--update-alt-gs\fP
`update-alternatives --get-selections` command parser
.TP
.B
\fB--update-alt-q\fP
`update-alternatives --query` command parser
.TP
.B
\fB--upower\fP

View File

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

View File

@ -107,11 +107,11 @@ pip3 install jc
### OS Package Repositories
| OS | Command |
|-----------------------|-------------------------------------------------------------------------------|
|--------------------------------------|-------------------------------------------------------------------------------|
| Debian/Ubuntu linux | `apt-get install jc` |
| Fedora linux | `dnf install jc` |
| openSUSE linux | `zypper install jc` |
| Arch linux | `pacman -S jc` |
| Archlinux User Repositories (AUR) | `paru -S jc` or `aura -A jc` or `yay -S jc` |
| NixOS linux | `nix-env -iA nixpkgs.jc` or `nix-env -iA nixos.jc` |
| Guix System linux | `guix install jc` |
| macOS | `brew install jc` |
@ -305,9 +305,9 @@ Local parser plugins are standard python module files. Use the
or [`jc/parsers/foo_s.py (streaming)`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo_s.py)
parser as a template and simply place a `.py` file in the `jcparsers` subfolder.
Local plugin filenames must be valid python module names, therefore must consist
entirely of alphanumerics and start with a letter. Local plugins may override
default parsers.
Local plugin filenames must be valid python module names and therefore must
start with a letter and consist entirely of alphanumerics. Local plugins
may override default parsers.
> Note: The application data directory follows the
[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

14966
tests/fixtures/generic/git-log-full.out vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

19832
tests/fixtures/generic/git-log-fuller.out vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

14966
tests/fixtures/generic/git-log-medium.out vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

2433
tests/fixtures/generic/git-log-oneline.out vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

12280
tests/fixtures/generic/git-log-short.out vendored Normal file

File diff suppressed because it is too large Load Diff

1
tests/fixtures/generic/git-log.json vendored Normal file

File diff suppressed because one or more lines are too long

14966
tests/fixtures/generic/git-log.out vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
[{"name":"arptables","status":"auto","current":"/usr/sbin/arptables-nft"},{"name":"awk","status":"auto","current":"/usr/bin/gawk"},{"name":"builtins.7.gz","status":"auto","current":"/usr/share/man/man7/bash-builtins.7.gz"},{"name":"c++","status":"auto","current":"/usr/bin/g++"},{"name":"c89","status":"auto","current":"/usr/bin/c89-gcc"},{"name":"c99","status":"auto","current":"/usr/bin/c99-gcc"},{"name":"cc","status":"auto","current":"/usr/bin/gcc"},{"name":"cpp","status":"auto","current":"/usr/bin/cpp"},{"name":"ebtables","status":"auto","current":"/usr/sbin/ebtables-nft"},{"name":"editor","status":"auto","current":"/bin/nano"},{"name":"ex","status":"auto","current":"/usr/bin/vim.basic"},{"name":"fakeroot","status":"auto","current":"/usr/bin/fakeroot-sysv"},{"name":"ftp","status":"auto","current":"/usr/bin/netkit-ftp"},{"name":"infobrowser","status":"auto","current":"/usr/bin/info"},{"name":"ip6tables","status":"auto","current":"/usr/sbin/ip6tables-nft"},{"name":"iptables","status":"auto","current":"/usr/sbin/iptables-nft"},{"name":"jsondiff","status":"auto","current":"/usr/bin/json-patch-jsondiff"},{"name":"lzma","status":"auto","current":"/usr/bin/xz"},{"name":"mt","status":"auto","current":"/bin/mt-gnu"},{"name":"nc","status":"auto","current":"/bin/nc.openbsd"},{"name":"newt-palette","status":"auto","current":"/etc/newt/palette.ubuntu"},{"name":"pager","status":"auto","current":"/usr/bin/less"},{"name":"pico","status":"auto","current":"/bin/nano"},{"name":"pinentry","status":"auto","current":"/usr/bin/pinentry-curses"},{"name":"rcp","status":"auto","current":"/usr/bin/scp"},{"name":"rlogin","status":"auto","current":"/usr/bin/slogin"},{"name":"rmt","status":"auto","current":"/usr/sbin/rmt-tar"},{"name":"rsh","status":"auto","current":"/usr/bin/ssh"},{"name":"rview","status":"auto","current":"/usr/bin/vim.basic"},{"name":"rvim","status":"auto","current":"/usr/bin/vim.basic"},{"name":"sar","status":"auto","current":"/usr/bin/sar.sysstat"},{"name":"telnet","status":"auto","current":"/usr/bin/telnet.netkit"},{"name":"text.plymouth","status":"auto","current":"/usr/share/plymouth/themes/ubuntu-text/ubuntu-text.plymouth"},{"name":"traceroute6","status":"auto","current":"/usr/bin/traceroute6.iputils"},{"name":"vi","status":"auto","current":"/usr/bin/vim.basic"},{"name":"view","status":"auto","current":"/usr/bin/vim.basic"},{"name":"vim","status":"auto","current":"/usr/bin/vim.basic"},{"name":"vimdiff","status":"auto","current":"/usr/bin/vim.basic"},{"name":"vtrgb","status":"auto","current":"/etc/console-setup/vtrgb"},{"name":"w","status":"auto","current":"/usr/bin/w.procps"},{"name":"write","status":"auto","current":"/usr/bin/write.ul"}]

View File

@ -0,0 +1,41 @@
arptables auto /usr/sbin/arptables-nft
awk auto /usr/bin/gawk
builtins.7.gz auto /usr/share/man/man7/bash-builtins.7.gz
c++ auto /usr/bin/g++
c89 auto /usr/bin/c89-gcc
c99 auto /usr/bin/c99-gcc
cc auto /usr/bin/gcc
cpp auto /usr/bin/cpp
ebtables auto /usr/sbin/ebtables-nft
editor auto /bin/nano
ex auto /usr/bin/vim.basic
fakeroot auto /usr/bin/fakeroot-sysv
ftp auto /usr/bin/netkit-ftp
infobrowser auto /usr/bin/info
ip6tables auto /usr/sbin/ip6tables-nft
iptables auto /usr/sbin/iptables-nft
jsondiff auto /usr/bin/json-patch-jsondiff
lzma auto /usr/bin/xz
mt auto /bin/mt-gnu
nc auto /bin/nc.openbsd
newt-palette auto /etc/newt/palette.ubuntu
pager auto /usr/bin/less
pico auto /bin/nano
pinentry auto /usr/bin/pinentry-curses
rcp auto /usr/bin/scp
rlogin auto /usr/bin/slogin
rmt auto /usr/sbin/rmt-tar
rsh auto /usr/bin/ssh
rview auto /usr/bin/vim.basic
rvim auto /usr/bin/vim.basic
sar auto /usr/bin/sar.sysstat
telnet auto /usr/bin/telnet.netkit
text.plymouth auto /usr/share/plymouth/themes/ubuntu-text/ubuntu-text.plymouth
traceroute6 auto /usr/bin/traceroute6.iputils
vi auto /usr/bin/vim.basic
view auto /usr/bin/vim.basic
vim auto /usr/bin/vim.basic
vimdiff auto /usr/bin/vim.basic
vtrgb auto /etc/console-setup/vtrgb
w auto /usr/bin/w.procps
write auto /usr/bin/write.ul

View File

@ -0,0 +1 @@
{"name":"editor","link":"/usr/bin/editor","slaves":[{"name":"editor.1.gz","path":"/usr/share/man/man1/editor.1.gz"},{"name":"editor.da.1.gz","path":"/usr/share/man/da/man1/editor.1.gz"},{"name":"editor.de.1.gz","path":"/usr/share/man/de/man1/editor.1.gz"},{"name":"editor.fr.1.gz","path":"/usr/share/man/fr/man1/editor.1.gz"},{"name":"editor.it.1.gz","path":"/usr/share/man/it/man1/editor.1.gz"},{"name":"editor.ja.1.gz","path":"/usr/share/man/ja/man1/editor.1.gz"},{"name":"editor.pl.1.gz","path":"/usr/share/man/pl/man1/editor.1.gz"},{"name":"editor.ru.1.gz","path":"/usr/share/man/ru/man1/editor.1.gz"}],"status":"auto","best":"/bin/nano","value":"/bin/nano","alternatives":[{"alternative":"/bin/ed","priority":-100,"slaves":[{"name":"editor.1.gz","path":"/usr/share/man/man1/ed.1.gz"}]},{"alternative":"/bin/nano","priority":40,"slaves":[{"name":"editor.1.gz","path":"/usr/share/man/man1/nano.1.gz"}]},{"alternative":"/usr/bin/vim.basic","priority":30,"slaves":[{"name":"editor.1.gz","path":"/usr/share/man/man1/vim.1.gz"},{"name":"editor.da.1.gz","path":"/usr/share/man/da/man1/vim.1.gz"},{"name":"editor.de.1.gz","path":"/usr/share/man/de/man1/vim.1.gz"},{"name":"editor.fr.1.gz","path":"/usr/share/man/fr/man1/vim.1.gz"},{"name":"editor.it.1.gz","path":"/usr/share/man/it/man1/vim.1.gz"},{"name":"editor.ja.1.gz","path":"/usr/share/man/ja/man1/vim.1.gz"},{"name":"editor.pl.1.gz","path":"/usr/share/man/pl/man1/vim.1.gz"},{"name":"editor.ru.1.gz","path":"/usr/share/man/ru/man1/vim.1.gz"}]},{"alternative":"/usr/bin/vim.tiny","priority":15,"slaves":[{"name":"editor.1.gz","path":"/usr/share/man/man1/vim.1.gz"},{"name":"editor.da.1.gz","path":"/usr/share/man/da/man1/vim.1.gz"},{"name":"editor.de.1.gz","path":"/usr/share/man/de/man1/vim.1.gz"},{"name":"editor.fr.1.gz","path":"/usr/share/man/fr/man1/vim.1.gz"},{"name":"editor.it.1.gz","path":"/usr/share/man/it/man1/vim.1.gz"},{"name":"editor.ja.1.gz","path":"/usr/share/man/ja/man1/vim.1.gz"},{"name":"editor.pl.1.gz","path":"/usr/share/man/pl/man1/vim.1.gz"},{"name":"editor.ru.1.gz","path":"/usr/share/man/ru/man1/vim.1.gz"}]}]}

View File

@ -0,0 +1,48 @@
Name: editor
Link: /usr/bin/editor
Slaves:
editor.1.gz /usr/share/man/man1/editor.1.gz
editor.da.1.gz /usr/share/man/da/man1/editor.1.gz
editor.de.1.gz /usr/share/man/de/man1/editor.1.gz
editor.fr.1.gz /usr/share/man/fr/man1/editor.1.gz
editor.it.1.gz /usr/share/man/it/man1/editor.1.gz
editor.ja.1.gz /usr/share/man/ja/man1/editor.1.gz
editor.pl.1.gz /usr/share/man/pl/man1/editor.1.gz
editor.ru.1.gz /usr/share/man/ru/man1/editor.1.gz
Status: auto
Best: /bin/nano
Value: /bin/nano
Alternative: /bin/ed
Priority: -100
Slaves:
editor.1.gz /usr/share/man/man1/ed.1.gz
Alternative: /bin/nano
Priority: 40
Slaves:
editor.1.gz /usr/share/man/man1/nano.1.gz
Alternative: /usr/bin/vim.basic
Priority: 30
Slaves:
editor.1.gz /usr/share/man/man1/vim.1.gz
editor.da.1.gz /usr/share/man/da/man1/vim.1.gz
editor.de.1.gz /usr/share/man/de/man1/vim.1.gz
editor.fr.1.gz /usr/share/man/fr/man1/vim.1.gz
editor.it.1.gz /usr/share/man/it/man1/vim.1.gz
editor.ja.1.gz /usr/share/man/ja/man1/vim.1.gz
editor.pl.1.gz /usr/share/man/pl/man1/vim.1.gz
editor.ru.1.gz /usr/share/man/ru/man1/vim.1.gz
Alternative: /usr/bin/vim.tiny
Priority: 15
Slaves:
editor.1.gz /usr/share/man/man1/vim.1.gz
editor.da.1.gz /usr/share/man/da/man1/vim.1.gz
editor.de.1.gz /usr/share/man/de/man1/vim.1.gz
editor.fr.1.gz /usr/share/man/fr/man1/vim.1.gz
editor.it.1.gz /usr/share/man/it/man1/vim.1.gz
editor.ja.1.gz /usr/share/man/ja/man1/vim.1.gz
editor.pl.1.gz /usr/share/man/pl/man1/vim.1.gz
editor.ru.1.gz /usr/share/man/ru/man1/vim.1.gz

View File

@ -43,6 +43,7 @@
"is_connected": true,
"is_primary": true,
"device_name": "eDP1",
"rotation": "normal",
"resolution_width": 1920,
"resolution_height": 1080,
"offset_width": 0,

215
tests/test_git_log.py Normal file
View File

@ -0,0 +1,215 @@
import os
import unittest
import json
import jc.parsers.git_log
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/git-log.out'), 'r', encoding='utf-8') as f:
self.git_log = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-short.out'), 'r', encoding='utf-8') as f:
self.git_log_short = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-short-stat.out'), 'r', encoding='utf-8') as f:
self.git_log_short_stat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-short-shortstat.out'), 'r', encoding='utf-8') as f:
self.git_log_short_shortstat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-medium.out'), 'r', encoding='utf-8') as f:
self.git_log_medium = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-medium-stat.out'), 'r', encoding='utf-8') as f:
self.git_log_medium_stat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-medium-shortstat.out'), 'r', encoding='utf-8') as f:
self.git_log_medium_shortstat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-full.out'), 'r', encoding='utf-8') as f:
self.git_log_full = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-full-stat.out'), 'r', encoding='utf-8') as f:
self.git_log_full_stat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-full-shortstat.out'), 'r', encoding='utf-8') as f:
self.git_log_full_shortstat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-fuller.out'), 'r', encoding='utf-8') as f:
self.git_log_fuller = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-fuller-stat.out'), 'r', encoding='utf-8') as f:
self.git_log_fuller_stat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-fuller-shortstat.out'), 'r', encoding='utf-8') as f:
self.git_log_fuller_shortstat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-oneline.out'), 'r', encoding='utf-8') as f:
self.git_log_oneline = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-oneline-stat.out'), 'r', encoding='utf-8') as f:
self.git_log_oneline_stat = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-oneline-shortstat.out'), 'r', encoding='utf-8') as f:
self.git_log_oneline_shortstat = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log.json'), 'r', encoding='utf-8') as f:
self.git_log_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-short.json'), 'r', encoding='utf-8') as f:
self.git_log_short_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-short-stat.json'), 'r', encoding='utf-8') as f:
self.git_log_short_stat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-short-shortstat.json'), 'r', encoding='utf-8') as f:
self.git_log_short_shortstat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-medium.json'), 'r', encoding='utf-8') as f:
self.git_log_medium_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-medium-stat.json'), 'r', encoding='utf-8') as f:
self.git_log_medium_stat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-medium-shortstat.json'), 'r', encoding='utf-8') as f:
self.git_log_medium_shortstat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-full.json'), 'r', encoding='utf-8') as f:
self.git_log_full_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-full-stat.json'), 'r', encoding='utf-8') as f:
self.git_log_full_stat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-full-shortstat.json'), 'r', encoding='utf-8') as f:
self.git_log_full_shortstat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-fuller.json'), 'r', encoding='utf-8') as f:
self.git_log_fuller_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-fuller-stat.json'), 'r', encoding='utf-8') as f:
self.git_log_fuller_stat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-fuller-shortstat.json'), 'r', encoding='utf-8') as f:
self.git_log_fuller_shortstat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-oneline.json'), 'r', encoding='utf-8') as f:
self.git_log_oneline_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-oneline-stat.json'), 'r', encoding='utf-8') as f:
self.git_log_oneline_stat_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/git-log-oneline-shortstat.json'), 'r', encoding='utf-8') as f:
self.git_log_oneline_shortstat_json = json.loads(f.read())
def test_git_log_nodata(self):
"""
Test 'git_log' with no data
"""
self.assertEqual(jc.parsers.git_log.parse('', quiet=True), [])
def test_git_log(self):
"""
Test 'git_log'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log, quiet=True), self.git_log_json)
def test_git_log_short(self):
"""
Test 'git_log --format=short'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_short, quiet=True), self.git_log_short_json)
def test_git_log_short_stat(self):
"""
Test 'git_log --format=short --stat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_short_stat, quiet=True), self.git_log_short_stat_json)
def test_git_log_short_shortstat(self):
"""
Test 'git_log --format=short --shortstat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_short_shortstat, quiet=True), self.git_log_short_shortstat_json)
def test_git_log_medium(self):
"""
Test 'git_log --format=medium'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_medium, quiet=True), self.git_log_medium_json)
def test_git_log_medium_stat(self):
"""
Test 'git_log --format=medium --stat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_medium_stat, quiet=True), self.git_log_medium_stat_json)
def test_git_log_medium_shortstat(self):
"""
Test 'git_log --format=medium --shortstat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_medium_shortstat, quiet=True), self.git_log_medium_shortstat_json)
def test_git_log_full(self):
"""
Test 'git_log --format=full'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_full, quiet=True), self.git_log_full_json)
def test_git_log_full_stat(self):
"""
Test 'git_log --format=full --stat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_full_stat, quiet=True), self.git_log_full_stat_json)
def test_git_log_full_shortstat(self):
"""
Test 'git_log --format=full --shortstat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_full_shortstat, quiet=True), self.git_log_full_shortstat_json)
def test_git_log_fuller(self):
"""
Test 'git_log --format=fuller'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_fuller, quiet=True), self.git_log_fuller_json)
def test_git_log_fuller_stat(self):
"""
Test 'git_log --format=fuller --stat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_fuller_stat, quiet=True), self.git_log_fuller_stat_json)
def test_git_log_fuller_shortstat(self):
"""
Test 'git_log --format=fuller --shortstat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_fuller_shortstat, quiet=True), self.git_log_fuller_shortstat_json)
def test_git_log_oneline(self):
"""
Test 'git_log --format=oneline'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_oneline, quiet=True), self.git_log_oneline_json)
def test_git_log_oneline_stat(self):
"""
Test 'git_log --format=oneline --stat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_oneline_stat, quiet=True), self.git_log_oneline_stat_json)
def test_git_log_oneline_shortstat(self):
"""
Test 'git_log --format=oneline --shortstat'
"""
self.assertEqual(jc.parsers.git_log.parse(self.git_log_oneline_shortstat, quiet=True), self.git_log_oneline_shortstat_json)
if __name__ == '__main__':
unittest.main()

View File

@ -41,6 +41,18 @@ class MyTests(unittest.TestCase):
"""
self.assertEqual(jc.parsers.ini.parse(self.generic_ini_iptelserver, quiet=True), self.generic_ini_iptelserver_json)
def test_ini_duplicate_keys(self):
"""
Test input that contains duplicate keys. Only the last value should be used.
"""
data = '''
duplicate_key: value1
another_key = foo
duplicate_key = value2
'''
expected = {'duplicate_key': 'value2', 'another_key': 'foo'}
self.assertEqual(jc.parsers.ini.parse(data, quiet=True), expected)
if __name__ == '__main__':
unittest.main()

View File

@ -41,6 +41,18 @@ class MyTests(unittest.TestCase):
"""
self.assertEqual(jc.parsers.kv.parse(self.generic_ini_keyvalue_ifcfg, quiet=True), self.generic_ini_keyvalue_ifcfg_json)
def test_kv_duplicate_keys(self):
"""
Test input that contains duplicate keys. Only the last value should be used.
"""
data = '''
duplicate_key: value1
another_key = foo
duplicate_key = value2
'''
expected = {'duplicate_key': 'value2', 'another_key': 'foo'}
self.assertEqual(jc.parsers.kv.parse(data, quiet=True), expected)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,35 @@
import os
import unittest
import json
import jc.parsers.update_alt_gs
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/update-alternatives-get-selections.out'), 'r', encoding='utf-8') as f:
self.update_alternatives_get_selections = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/update-alternatives-get-selections.json'), 'r', encoding='utf-8') as f:
self.update_alternatives_get_selections_json = json.loads(f.read())
def test_update_alt_gs_nodata(self):
"""
Test 'update-alternatives --get-selections' with no data
"""
self.assertEqual(jc.parsers.update_alt_gs.parse('', quiet=True), [])
def test_update_alt_gs(self):
"""
Test 'update-alternatives --get-selections'
"""
self.assertEqual(jc.parsers.update_alt_gs.parse(self.update_alternatives_get_selections, quiet=True), self.update_alternatives_get_selections_json)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,35 @@
import os
import unittest
import json
import jc.parsers.update_alt_q
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/update-alternatives-query.out'), 'r', encoding='utf-8') as f:
self.update_alternatives_query = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/generic/update-alternatives-query.json'), 'r', encoding='utf-8') as f:
self.update_alternatives_query_json = json.loads(f.read())
def test_update_alt_q_nodata(self):
"""
Test 'update-alternatives --query' with no data
"""
self.assertEqual(jc.parsers.update_alt_q.parse('', quiet=True), {})
def test_update_alt_q(self):
"""
Test 'update-alternatives --query'
"""
self.assertEqual(jc.parsers.update_alt_q.parse(self.update_alternatives_query, quiet=True), self.update_alternatives_query_json)
if __name__ == '__main__':
unittest.main()

View File

@ -30,7 +30,8 @@ class XrandrTests(unittest.TestCase):
"HDMI1 connected (normal left inverted right x axis y axis)",
"VIRTUAL1 disconnected (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",
"eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm"
"eDP-1 connected primary 1920x1080+0+0 (normal left inverted right x axis y axis) 309mm x 174mm",
"HDMI-0 connected 2160x3840+3840+0 right (normal left inverted right x axis y axis) 609mm x 349mm",
]
for device in devices:
self.assertIsNotNone(re.match(_device_pattern, device))
@ -85,7 +86,7 @@ class XrandrTests(unittest.TestCase):
def test_device(self):
# regex101 sample link for tests/edits https://regex101.com/r/3cHMv3/1
sample = "eDP1 connected primary 1920x1080+0+0 (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])
expected = {
@ -98,6 +99,7 @@ class XrandrTests(unittest.TestCase):
"offset_height": 0,
"dimension_width": 310,
"dimension_height": 170,
"rotation": "left",
}
self.assertIsNotNone(actual)

View File

@ -41,6 +41,14 @@ class MyTests(unittest.TestCase):
"""
self.assertEqual(jc.parsers.yaml.parse(self.generic_yaml_istio_sidecar, quiet=True), self.generic_yaml_istio_sidecar_json)
def test_yaml_datetime(self):
"""
Test yaml file with datetime object (should convert to a string)
"""
data = 'deploymentTime: 2022-04-18T11:12:47'
expected = [{"deploymentTime":"2022-04-18T11:12:47"}]
self.assertEqual(jc.parsers.yaml.parse(data, quiet=True), expected)
if __name__ == '__main__':
unittest.main()

View File

@ -1,11 +1,21 @@
#!/bin/bash
# Update all documentation (README.md, Man page, Doc files)
echo === Building README.md
./readmegen.py && echo "+++ README.md build successful" || echo "--- README.md build failed"
(
echo === Building README.md
./readmegen.py && echo "++++ README.md build successful" || echo "---- README.md build failed"
) &
echo === Building man page
./mangen.py && echo "+++ man page build successful" || echo "--- man page build failed"
(
echo === Building man page
./mangen.py && echo "++++ man page build successful" || echo "---- man page build failed"
) &
echo === Building documentation
./docgen.sh && echo "+++ documentation build successful" || echo "--- documentation build failed"
(
echo === Building documentation
./docgen.sh && echo "++++ documentation build successful" || echo "---- documentation build failed"
) &
wait
echo
echo "All documentation updated"