1
0
mirror of https://github.com/kellyjonbrazil/jc.git synced 2026-04-03 17:44:07 +02:00

Compare commits

...

82 Commits

Author SHA1 Message Date
Kelly Brazil
f5627a4594 version bump 2020-05-11 11:03:02 -07:00
Kelly Brazil
81ffdb2510 remove shebang for Fedora packaging 2020-05-11 10:54:26 -07:00
Kelly Brazil
4c00a99850 version bump 2020-05-11 10:37:14 -07:00
Kelly Brazil
2bfcb45b28 make cli.py executable 2020-05-11 10:32:42 -07:00
Kelly Brazil
ab0c10e791 remove execute permissions 2020-05-11 10:27:16 -07:00
Kelly Brazil
2c1935115d Merge pull request #61 from kellyjonbrazil/dev
Dev v1.10.10
2020-05-09 11:47:05 -07:00
Kelly Brazil
d98e43dc78 add netstat item 2020-05-09 11:42:56 -07:00
Kelly Brazil
9348988d64 add netstat test for Fedora32 2020-05-09 11:41:30 -07:00
Kelly Brazil
1285c66467 netstat with bluetooth section 2020-05-09 11:36:23 -07:00
Kelly Brazil
b7191bbc13 handle bluetooth section (ignore for now) 2020-05-09 11:36:03 -07:00
Kelly Brazil
98b97509f7 version bump 2020-05-09 11:25:13 -07:00
Kelly Brazil
2b2b570490 add tests for 'gone - no logout' 2020-05-09 11:22:26 -07:00
Kelly Brazil
cce2d1ff29 add condition for 'gone - no logout' 2020-05-09 11:22:01 -07:00
Kelly Brazil
b79600c572 version bump 2020-05-09 11:01:48 -07:00
Kelly Brazil
140f1a8543 test fixes for issue #60 2020-05-09 11:00:04 -07:00
Kelly Brazil
e34657cfde fix issue #60 that was skipping the first file in some instances using -R without -l 2020-05-08 15:26:11 -07:00
Kelly Brazil
99070fa607 version bump 2020-05-08 10:51:13 -07:00
Kelly Brazil
2b46785b1f add MIT license to vendorized IfconfigParser class 2020-05-08 10:49:30 -07:00
Kelly Brazil
c72562524b fully remove tests from packaging 2020-05-08 09:30:31 -07:00
Kelly Brazil
b7dd6441c7 version bump 2020-05-08 08:20:33 -07:00
Kelly Brazil
31fcc2f755 remove manifest.in - no longer needed due to removing tests 2020-05-08 08:20:21 -07:00
Kelly Brazil
b391aa14bc add license_file to metadata 2020-05-08 08:19:51 -07:00
Kelly Brazil
d3c45debbb remove tests and add license file 2020-05-08 08:19:31 -07:00
Kelly Brazil
5b08469b87 Merge pull request #57 from kellyjonbrazil/dev
Dev v1.10.7
2020-05-01 15:55:50 -07:00
Kelly Brazil
4a77ec63a4 add IfconfigParser class 2020-05-01 14:57:50 -07:00
Kelly Brazil
d13606b6dc modify dependencies for easier packaging into Fedora 2020-05-01 14:37:23 -07:00
Kelly Brazil
05291c93bb vendorize ifconfig-parser module for easier packaging in Fedora 2020-05-01 14:36:54 -07:00
Kelly Brazil
8cf00a208e change text to strings 2020-04-29 15:57:55 -07:00
Kelly Brazil
06d73c8876 formatting 2020-04-23 07:06:44 -07:00
Kelly Brazil
649c646ea2 add brew install option 2020-04-22 16:08:34 -07:00
Kelly Brazil
b7756d9250 version bump 2020-04-20 16:33:26 -07:00
Kelly Brazil
1cd2cd954c remove references to homebrew/shim to allow tests to pass in homebrew packaging ci/cd 2020-04-20 16:31:22 -07:00
Kelly Brazil
72020b8da9 move packages info to jc-packages github page 2020-04-17 10:20:25 -07:00
Kelly Brazil
cf9720b749 update install info 2020-04-16 14:03:31 -07:00
Kelly Brazil
967b9db7f9 spelling 2020-04-15 21:27:22 -07:00
Kelly Brazil
bb3acb1182 formatting 2020-04-15 21:25:06 -07:00
Kelly Brazil
560c7f7e6d formatting 2020-04-15 21:23:55 -07:00
Kelly Brazil
79b2841764 add new binary package install info 2020-04-15 21:22:43 -07:00
Kelly Brazil
a06a89cbd1 version bump 2020-04-14 11:15:24 -07:00
Kelly Brazil
431bd969eb use sys.exit(0) instead of exit() 2020-04-14 11:10:31 -07:00
Kelly Brazil
c87b722aec spelling 2020-04-12 13:23:58 -07:00
Kelly Brazil
3688b8b014 Merge pull request #56 from kellyjonbrazil/dev
Dev v1.10.4
2020-04-12 13:21:38 -07:00
Kelly Brazil
07b8d9e0c0 version bump 2020-04-12 13:18:28 -07:00
Kelly Brazil
7454b53e39 formatting 2020-04-12 13:13:28 -07:00
Kelly Brazil
3d6a76024d update with JC_COLORS info 2020-04-12 13:10:57 -07:00
Kelly Brazil
421b980957 JC_COLORS working 2020-04-12 13:03:09 -07:00
Kelly Brazil
4a22e27d6a add set_env_colors function 2020-04-12 12:43:51 -07:00
Kelly Brazil
99f7842dee fix brek on pipe error 2020-04-09 13:38:33 -07:00
Kelly Brazil
7f869b4b18 change colors to ansi and match jello style 2020-04-09 07:31:21 -07:00
Kelly Brazil
9665f4ee84 add pypi badge 2020-04-07 08:44:15 -07:00
Kelly Brazil
606904d48b Merge pull request #53 from kellyjonbrazil/dev
Dev v1.10.1
2020-04-04 17:28:21 -07:00
Kelly Brazil
3f5279b97c version bump to 1.10.1 2020-04-04 17:25:55 -07:00
Kelly Brazil
f5ec21e6ac use in instead of find() 2020-04-04 17:19:32 -07:00
Kelly Brazil
578a284465 use in instead of find() 2020-04-04 17:18:39 -07:00
Kelly Brazil
422e392d9d use in instead of find() 2020-04-04 17:17:43 -07:00
Kelly Brazil
54dfffd34a use in instead of find() 2020-04-04 17:16:25 -07:00
Kelly Brazil
cffba64d2b use in and startswith() instead of find() 2020-04-04 17:15:03 -07:00
Kelly Brazil
56a0c12a59 use in instead of find() 2020-04-04 17:12:22 -07:00
Kelly Brazil
c174d3de18 use in and startswith() instead of find() 2020-04-04 17:10:46 -07:00
Kelly Brazil
a9c59ef9fc fix logic to not for ' type ' in cleandata[0] 2020-04-04 17:07:38 -07:00
Kelly Brazil
abdb9b2673 use in instead of find() 2020-04-04 17:05:17 -07:00
Kelly Brazil
548aaab626 remove old commented code 2020-04-04 17:04:20 -07:00
Kelly Brazil
20571c87ae us in instead of find() 2020-04-04 17:01:56 -07:00
Kelly Brazil
19e49200de version bump 2020-04-04 16:59:48 -07:00
Kelly Brazil
d32f5c67a9 use startswith() instead of find() 2020-04-04 16:59:03 -07:00
Kelly Brazil
b83b626435 use startswith() instead of find() 2020-04-04 16:57:23 -07:00
Kelly Brazil
ab2c1b25ec use startswith() and in instead of find() 2020-04-04 16:56:11 -07:00
Kelly Brazil
f2d46313a4 use startswith() instead of find() 2020-04-04 16:53:55 -07:00
Kelly Brazil
87e4796a6c use in instead of .find() 2020-04-04 16:52:45 -07:00
Kelly Brazil
0014a5c2f4 us startswith() and in instead of .find() 2020-04-04 16:51:36 -07:00
Kelly Brazil
7af56e0dad use startswith() and in instead of find() 2020-04-04 16:50:05 -07:00
Kelly Brazil
a5ae6e3c01 use startswith() instead of find() 2020-04-04 16:48:16 -07:00
Kelly Brazil
fe1a0d1faf use in instead of .find() 2020-04-04 16:46:09 -07:00
Kelly Brazil
302f05cdda prettify style block 2020-04-03 14:50:20 -07:00
Kelly Brazil
c0044be7b0 rename color grey to gray 2020-04-03 14:48:19 -07:00
Kelly Brazil
0110078807 update badge 2020-04-03 14:47:08 -07:00
Kelly Brazil
42eacb45f8 rename to Tests 2020-04-03 14:45:04 -07:00
Kelly Brazil
a43e2e1991 add -m option info 2020-04-02 17:40:06 -07:00
Kelly Brazil
c8b721d4f6 version bump to 1.10.0 2020-04-02 17:35:35 -07:00
Kelly Brazil
d0bfddc3d9 add color and -m monochrome option 2020-04-02 17:29:25 -07:00
Kelly Brazil
6b925a16c8 add tests badge 2020-04-02 10:55:32 -07:00
Kelly Brazil
89ebd9fc22 add axfr info to schema 2020-03-27 07:31:08 -07:00
49 changed files with 689 additions and 150 deletions

View File

@@ -1,4 +1,4 @@
name: Test code
name: Tests
on:
push:

0
LICENSE.md Executable file → Normal file
View File

View File

@@ -1 +0,0 @@
graft tests/fixtures

33
README.md Executable file → Normal file
View File

@@ -1,3 +1,6 @@
![Tests](https://github.com/kellyjonbrazil/jc/workflows/Tests/badge.svg?branch=master)
![Pypi](https://img.shields.io/pypi/v/jc.svg)
# JC
JSON CLI output utility
@@ -66,10 +69,21 @@ Release notes can be found [here](https://blog.kellybrazil.com/category/jc-news/
For more information on the motivations for this project, please see my [blog post](https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/).
## Installation
There are several ways to get `jc`. You can install via `pip`, `brew`, DEB or RPM packages, or by downloading the correct binary for your architecture and running it anywhere on your filesystem.
### Pip (macOS, linux, unix, Windows)
```
$ pip3 install --upgrade jc
```
### Brew (macOS)
```
$ brew install jc
```
### Packages and Binaries
Please see https://kellyjonbrazil.github.io/jc-packaging/ for details.
## Usage
`jc` accepts piped input from `STDIN` and outputs a JSON representation of the previous command's output to `STDOUT`.
```
@@ -136,9 +150,26 @@ The JSON output can be compact (default) or pretty formatted with the `-p` optio
### Options
- `-a` about `jc`. Prints information about `jc` and the parsers (in JSON, of course!)
- `-d` debug mode. Prints trace messages if parsing issues encountered
- `-m` monochrome JSON output
- `-p` pretty format the JSON output
- `-q` quiet mode. Suppresses warning messages
- `-r` raw output. Provides a more literal JSON output with all values as text and no additional sematic processing
- `-r` raw output. Provides a more literal JSON output with all values as strings and no additional sematic processing
### Setting Custom Colors via Environment Variable
You can specify custom colors via the `JC_COLORS` environment variable. The `JC_COLORS` environment variable takes four comma separated string values in the following format:
```
JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color>
```
Where colors are: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `gray`, `brightblack`, `brightred`, `brightgreen`, `brightyellow`, `brightblue`, `brightmagenta`, `brightcyan`, `white`, or `default`
For example, to set to the default colors:
```
JC_COLORS=blue,brightblack,magenta,green
```
or
```
JC_COLORS=default,default,default,default
```
## Contributions
Feel free to add/improve code or parsers! You can use the [`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py) parser as a template and submit your parser with a pull request.

View File

@@ -1,5 +1,48 @@
jc changelog
20200511 v1.10.12
- Remove shebang from jc/cli.py for Fedora packaging
20200511 v1.10.11
- Change file permissions for Fedora packaging
20200509 v1.10.10
- Fix ls parser issue where the first file was skipped for ls -R on some platforms
- Update last parser to handle 'gone - no logout' condition
- Update netstat parser to handle bluetooth section (ignore gracefully for now)
20200508 v1.10.9
- Add license info to vendorized ifconfig-parser class
20200508 v1.10.8
- Add license file to dist for Fedora RPM packaging requirements
- Remove tests from package to keep from polluting the global site-packages
20200501 v1.10.7
- Requirements modifications for Fedora RPM packaging requirements
20200420 v1.10.6
- Remove homebrew shim references from du osx tests
20200414 v1.10.5
- Minor change of using sys.exit(0) instead of exit()
20200412 v1.10.4
- Add color customization via JC_COLORS env variable
20200409 v1.10.3
- Fix break on pipe error
20200409 v1.10.2
- Change colors to ansi and match jello colors
20200402 v1.10.1
- Code cleanup
20200402 v1.10.0
- Add color output by default when not piping data to another program
- Add -m option for monochrome output
20200326 v1.9.3
- Add axfr support for dig command parser

View File

@@ -353,6 +353,15 @@ Returns:
"answer_num": integer,
"authority_num": integer,
"additional_num": integer,
"axfr": [
{
"name": string,
"class": string,
"type": string,
"ttl": integer,
"data": string
}
],
"question": {
"name": string,
"class": string,
@@ -380,6 +389,7 @@ Returns:
"server": string,
"when": string,
"rcvd": integer
"size": string
}
]

View File

@@ -147,6 +147,17 @@ Examples:
info(self, /, *args, **kwargs)
```
## IfconfigParser
```python
IfconfigParser(self, console_output)
```
## InterfaceNotFound
```python
InterfaceNotFound(self, /, *args, **kwargs)
```
## process
```python
process(proc_data)

100
jc/cli.py
View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python3
"""jc - JSON CLI output utility
JC cli module
"""
@@ -9,11 +8,16 @@ import importlib
import textwrap
import signal
import json
from pygments import highlight
from pygments.style import Style
from pygments.token import (Name, Number, String, Keyword)
from pygments.lexers import JsonLexer
from pygments.formatters import Terminal256Formatter
import jc.utils
class info():
version = '1.9.3'
version = '1.10.12'
description = 'jc cli output JSON conversion tool'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -75,6 +79,63 @@ parsers = [
]
def set_env_colors():
"""
Grab custom colors from JC_COLORS environment variable. JC_COLORS env variable takes 4 comma
separated string values and should be in the format of:
JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color>
Where colors are: black, red, green, yellow, blue, magenta, cyan, gray, brightblack, brightred,
brightgreen, brightyellow, brightblue, brightmagenta, brightcyan, white, default
Default colors:
JC_COLORS=blue,brightblack,magenta,green
or
JC_COLORS=default,default,default,default
"""
env_colors = os.getenv('JC_COLORS')
input_error = False
if env_colors:
color_list = env_colors.split(',')
else:
input_error = True
if env_colors and len(color_list) != 4:
print('jc: Warning: could not parse JC_COLORS environment variable\n', file=sys.stderr)
input_error = True
if env_colors:
for color in color_list:
if color not in ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'gray', 'brightblack', 'brightred',
'brightgreen', 'brightyellow', 'brightblue', 'brightmagenta', 'brightcyan', 'white', 'default']:
print('jc: Warning: could not parse JC_COLORS environment variable\n', file=sys.stderr)
input_error = True
# if there is an issue with the env variable, just set all colors to default and move on
if input_error:
color_list = ['default', 'default', 'default', 'default']
# Try the color set in the JC_COLORS env variable first. If it is set to default, then fall back to default colors
return {
Name.Tag: f'bold ansi{color_list[0]}' if not color_list[0] == 'default' else f'bold ansiblue', # key names
Keyword: f'ansi{color_list[1]}' if not color_list[1] == 'default' else f'ansibrightblack', # true, false, null
Number: f'ansi{color_list[2]}' if not color_list[2] == 'default' else f'ansimagenta', # numbers
String: f'ansi{color_list[3]}' if not color_list[3] == 'default' else f'ansigreen' # strings
}
def piped_output():
"""returns False if stdout is a TTY. True if output is being piped to another program"""
if sys.stdout.isatty():
return False
else:
return True
def ctrlc(signum, frame):
"""exit with error on SIGINT"""
sys.exit(1)
@@ -167,6 +228,7 @@ def helptext(message):
Options:
-a about jc
-d debug - show trace messages
-m monochrome output
-p pretty print output
-q quiet - suppress warnings
-r raw JSON output
@@ -181,11 +243,22 @@ def helptext(message):
print(textwrap.dedent(helptext_string), file=sys.stderr)
def json_out(data, pretty=False):
if pretty:
print(json.dumps(data, indent=2))
def json_out(data, pretty=False, mono=False, piped_out=False):
# set colors
class JcStyle(Style):
styles = set_env_colors()
if not mono and not piped_out:
if pretty:
print(highlight(json.dumps(data, indent=2), JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
else:
print(highlight(json.dumps(data), JsonLexer(), Terminal256Formatter(style=JcStyle))[0:-1])
else:
print(json.dumps(data))
if pretty:
print(json.dumps(data, indent=2))
else:
print(json.dumps(data))
def generate_magic_command(args):
@@ -248,7 +321,7 @@ def magic():
valid_command, run_command = generate_magic_command(sys.argv)
if valid_command:
os.system(run_command)
exit()
sys.exit(0)
elif run_command is None:
return
else:
@@ -260,6 +333,12 @@ def main():
# break on ctrl-c keyboard interrupt
signal.signal(signal.SIGINT, ctrlc)
# break on pipe error. need try/except for windows compatibility
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
# try magic syntax first: e.g. jc -p ls -al
magic()
@@ -271,13 +350,14 @@ def main():
options.extend(opt[1:])
debug = 'd' in options
mono = 'm' in options
pretty = 'p' in options
quiet = 'q' in options
raw = 'r' in options
if 'a' in options:
json_out(about_jc(), pretty=pretty)
exit()
json_out(about_jc(), pretty=pretty, mono=mono, piped_out=piped_output())
sys.exit(0)
if sys.stdin.isatty():
helptext('missing piped data')
@@ -317,7 +397,7 @@ def main():
helptext('missing or incorrect arguments')
sys.exit(1)
json_out(result, pretty=pretty)
json_out(result, pretty=pretty, mono=mono, piped_out=piped_output())
if __name__ == '__main__':

View File

@@ -91,7 +91,7 @@ import jc.parsers.universal
class info():
version = '1.2'
version = '1.3'
description = 'arp command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -156,7 +156,7 @@ def parse(data, raw=False, quiet=False):
cleandata = data.splitlines()
# remove final Entries row if -v was used
if cleandata[-1].find('Entries:') == 0:
if cleandata[-1].startswith('Entries:'):
cleandata.pop(-1)
# detect if osx style was used
@@ -179,7 +179,7 @@ def parse(data, raw=False, quiet=False):
return process(raw_output)
# detect if linux style was used
elif cleandata[0].find('Address') == 0:
elif cleandata[0].startswith('Address'):
# fix header row to change Flags Mask to flags_mask
cleandata[0] = cleandata[0].replace('Flags Mask', 'flags_mask')

View File

@@ -132,7 +132,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.2'
description = 'crontab command and file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -227,13 +227,13 @@ def parse(data, raw=False, quiet=False):
# Clear any commented lines
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().find('#') == 0:
if line.strip().startswith('#'):
cleandata.pop(i)
# Pop any variable assignment lines
cron_var = []
for i, line in reversed(list(enumerate(cleandata))):
if line.find('=') != -1:
if '=' in line:
var_line = cleandata.pop(i)
var_name = var_line.split('=', maxsplit=1)[0].strip()
var_value = var_line.split('=', maxsplit=1)[1].strip()

View File

@@ -133,7 +133,7 @@ import jc.parsers.universal
class info():
version = '1.0'
version = '1.1'
description = 'crontab file parser with user support'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -228,13 +228,13 @@ def parse(data, raw=False, quiet=False):
# Clear any commented lines
for i, line in reversed(list(enumerate(cleandata))):
if line.strip().find('#') == 0:
if line.strip().startswith('#'):
cleandata.pop(i)
# Pop any variable assignment lines
cron_var = []
for i, line in reversed(list(enumerate(cleandata))):
if line.find('=') != -1:
if '=' in line:
var_line = cleandata.pop(i)
var_name = var_line.split('=', maxsplit=1)[0].strip()
var_value = var_line.split('=', maxsplit=1)[1].strip()

View File

@@ -73,7 +73,7 @@ import jc.parsers.universal
class info():
version = '1.1'
version = '1.2'
description = 'df command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -135,7 +135,7 @@ def process(proc_data):
# change any entry for key with '_blocks' in the name to int
for k in entry:
if str(k).find('_blocks') != -1:
if '_blocks' in str(k):
try:
blocks_int = int(entry[k])
entry[k] = blocks_int

View File

@@ -324,7 +324,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.2'
description = 'dig command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -361,6 +361,15 @@ def process(proc_data):
"answer_num": integer,
"authority_num": integer,
"additional_num": integer,
"axfr": [
{
"name": string,
"class": string,
"type": string,
"ttl": integer,
"data": string
}
],
"question": {
"name": string,
"class": string,
@@ -388,6 +397,7 @@ def process(proc_data):
"server": string,
"when": string,
"rcvd": integer
"size": string
}
]
"""
@@ -515,11 +525,12 @@ def parse_answer(answer):
'ttl': answer_ttl,
'data': answer_data}
def parse_axfr(axfr):
#; <<>> DiG 9.11.14-3-Debian <<>> @81.4.108.41 axfr zonetransfer.me +nocookie
#; (1 server found)
#;; global options: +cmd
#zonetransfer.me. 7200 IN A 5.196.105.14
# ; <<>> DiG 9.11.14-3-Debian <<>> @81.4.108.41 axfr zonetransfer.me +nocookie
# ; (1 server found)
# ;; global options: +cmd
# zonetransfer.me. 7200 IN A 5.196.105.14
axfr = axfr.split(maxsplit=4)
axfr_name = axfr[0]
axfr_ttl = axfr[1]
@@ -565,7 +576,7 @@ def parse(data, raw=False, quiet=False):
output_entry = {}
for line in cleandata:
if line.startswith('; <<>> ') and line.lower().find(' axfr ') != -1:
if line.startswith('; <<>> ') and ' axfr ' in line.lower():
question = False
authority = False
answer = False
@@ -573,7 +584,7 @@ def parse(data, raw=False, quiet=False):
axfr_list = []
continue
if line.find(';') == -1 and axfr:
if ';' not in line and axfr:
axfr_list.append(parse_axfr(line))
output_entry.update({'axfr': axfr_list})
continue
@@ -610,7 +621,7 @@ def parse(data, raw=False, quiet=False):
authority_list = []
continue
if line.find(';') == -1 and authority:
if ';' not in line and authority:
authority_list.append(parse_authority(line))
output_entry.update({'authority': authority_list})
continue
@@ -623,7 +634,7 @@ def parse(data, raw=False, quiet=False):
answer_list = []
continue
if line.find(';') == -1 and answer:
if ';' not in line and answer:
answer_list.append(parse_answer(line))
output_entry.update({'answer': answer_list})
continue

View File

@@ -70,7 +70,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'fstab file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -145,7 +145,7 @@ def parse(data, raw=False, quiet=False):
for line in cleandata:
output_line = {}
# ignore commented lines
if line.strip().find('#') == 0:
if line.strip().startswith('#'):
continue
line_list = line.split(maxsplit=6)

View File

@@ -61,7 +61,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = '/etc/hosts file parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -126,7 +126,7 @@ def parse(data, raw=False, quiet=False):
for line in cleandata:
output_line = {}
# ignore commented lines
if line.strip().find('#') == 0:
if line.strip().startswith('#'):
continue
line_list = line.split(maxsplit=1)
@@ -136,7 +136,7 @@ def parse(data, raw=False, quiet=False):
comment_found = False
for i, item in enumerate(hosts_list):
if item.find('#') != -1:
if '#' in item:
comment_found = True
comment_item = i
break

View File

@@ -141,16 +141,17 @@ Examples:
}
]
"""
import re
from collections import namedtuple
import jc.utils
from ifconfigparser import IfconfigParser
class info():
version = '1.5'
version = '1.7'
description = 'ifconfig command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
details = 'Using ifconfig-parser package from https://github.com/KnightWhoSayNi/ifconfig-parser'
details = 'Using ifconfig-parser from https://github.com/KnightWhoSayNi/ifconfig-parser'
# compatible options: linux, darwin, cygwin, win32, aix, freebsd
compatible = ['linux', 'aix', 'freebsd', 'darwin']
@@ -160,6 +161,222 @@ class info():
__version__ = info.version
class IfconfigParser(object):
# Author: threeheadedknight@protonmail.com
# Date created: 30.06.2018 17:03
# Python Version: 3.7
# MIT License
# Copyright (c) 2018 threeheadedknight@protonmail.com
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
attributes = ['name', 'type', 'mac_addr', 'ipv4_addr', 'ipv4_bcast', 'ipv4_mask', 'ipv6_addr', 'ipv6_mask',
'ipv6_scope', 'state', 'mtu', 'metric', 'rx_packets', 'rx_errors', 'rx_dropped', 'rx_overruns',
'rx_frame', 'tx_packets', 'tx_errors', 'tx_dropped', 'tx_overruns', 'tx_carrier', 'tx_collisions',
'rx_bytes', 'tx_bytes']
def __init__(self, console_output):
"""
:param console_output:
"""
if isinstance(console_output, list):
source_data = " ".join(console_output)
else:
source_data = console_output.replace("\n", " ")
self.interfaces = self.parser(source_data=source_data)
def list_interfaces(self):
"""
:return:
"""
return sorted(self.interfaces.keys())
def count_interfaces(self):
"""
:return:
"""
return len(self.interfaces.keys())
def filter_interfaces(self, **kwargs):
"""
:param kwargs:
:return:
"""
for attr in kwargs.keys():
if attr not in IfconfigParser.attributes:
raise ValueError("Attribute [{}] not supported.".format(attr))
filtered_interfaces = []
for name, details in self.interfaces.items():
if all(getattr(details, attr) == kwargs[attr] for attr in kwargs.keys()):
filtered_interfaces.append(name)
return sorted(filtered_interfaces)
def get_interface(self, name):
"""
:param name:
:return:
"""
if name in self.list_interfaces():
return self.interfaces[name]
else:
raise InterfaceNotFound("Interface [{}] not found.".format(name))
def get_interfaces(self):
"""
:return:
"""
return self.interfaces
def is_available(self, name):
"""
:param name:
:return:
"""
return name in self.interfaces
def parser(self, source_data):
"""
:param source_data:
:return:
"""
# Linux syntax
re_linux_interface = re.compile(
r"(?P<name>[a-zA-Z0-9:._-]+)\s+Link encap:(?P<type>\S+\s?\S+)(\s+HWaddr\s+\b"
r"(?P<mac_addr>[0-9A-Fa-f:?]+))?",
re.I)
re_linux_ipv4 = re.compile(
r"inet addr:(?P<ipv4_addr>(?:[0-9]{1,3}\.){3}[0-9]{1,3})(\s+Bcast:"
r"(?P<ipv4_bcast>(?:[0-9]{1,3}\.){3}[0-9]{1,3}))?\s+Mask:(?P<ipv4_mask>(?:[0-9]{1,3}\.){3}[0-9]{1,3})",
re.I)
re_linux_ipv6 = re.compile(
r"inet6 addr:\s+(?P<ipv6_addr>\S+)/(?P<ipv6_mask>[0-9]+)\s+Scope:(?P<ipv6_scope>Link|Host)",
re.I)
re_linux_state = re.compile(
r"\W+(?P<state>(?:\w+\s)+)(?:\s+)?MTU:(?P<mtu>[0-9]+)\s+Metric:(?P<metric>[0-9]+)", re.I)
re_linux_rx = re.compile(
r"RX packets:(?P<rx_packets>[0-9]+)\s+errors:(?P<rx_errors>[0-9]+)\s+dropped:"
r"(?P<rx_dropped>[0-9]+)\s+overruns:(?P<rx_overruns>[0-9]+)\s+frame:(?P<rx_frame>[0-9]+)",
re.I)
re_linux_tx = re.compile(
r"TX packets:(?P<tx_packets>[0-9]+)\s+errors:(?P<tx_errors>[0-9]+)\s+dropped:"
r"(?P<tx_dropped>[0-9]+)\s+overruns:(?P<tx_overruns>[0-9]+)\s+carrier:(?P<tx_carrier>[0-9]+)",
re.I)
re_linux_bytes = re.compile(r"\W+RX bytes:(?P<rx_bytes>\d+)\s+\(.*\)\s+TX bytes:(?P<tx_bytes>\d+)\s+\(.*\)", re.I)
re_linux_tx_stats = re.compile(r"collisions:(?P<tx_collisions>[0-9]+)\s+txqueuelen:[0-9]+", re.I)
re_linux = [re_linux_interface, re_linux_ipv4, re_linux_ipv6, re_linux_state, re_linux_rx, re_linux_tx,
re_linux_bytes, re_linux_tx_stats]
# OpenBSD syntax
re_openbsd_interface = re.compile(
r"(?P<name>[a-zA-Z0-9:._-]+):\s+flags=(?P<flags>[0-9]+)<(?P<state>\S+)?>\s+mtu\s+(?P<mtu>[0-9]+)",
re.I)
re_openbsd_ipv4 = re.compile(
r"inet (?P<ipv4_addr>(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s+netmask\s+"
r"(?P<ipv4_mask>(?:[0-9]{1,3}\.){3}[0-9]{1,3})(\s+broadcast\s+"
r"(?P<ipv4_bcast>(?:[0-9]{1,3}\.){3}[0-9]{1,3}))?",
re.I)
re_openbsd_ipv6 = re.compile(
r"inet6\s+(?P<ipv6_addr>\S+)\s+prefixlen\s+(?P<ipv6_mask>[0-9]+)\s+scopeid\s+(?P<ipv6_scope>\w+x\w+)<"
r"(?:link|host)>",
re.I)
re_openbsd_details = re.compile(
r"\S+\s+(?:(?P<mac_addr>[0-9A-Fa-f:?]+)\s+)?txqueuelen\s+[0-9]+\s+\((?P<type>\S+\s?\S+)\)", re.I)
re_openbsd_rx = re.compile(r"RX packets (?P<rx_packets>[0-9]+)\s+bytes\s+(?P<rx_bytes>\d+)\s+.*", re.I)
re_openbsd_rx_stats = re.compile(
r"RX errors (?P<rx_errors>[0-9]+)\s+dropped\s+(?P<rx_dropped>[0-9]+)\s+overruns\s+"
r"(?P<rx_overruns>[0-9]+)\s+frame\s+(?P<rx_frame>[0-9]+)",
re.I)
re_openbsd_tx = re.compile(r"TX packets (?P<tx_packets>[0-9]+)\s+bytes\s+(?P<tx_bytes>\d+)\s+.*", re.I)
re_openbsd_tx_stats = re.compile(
r"TX errors (?P<tx_errors>[0-9]+)\s+dropped\s+(?P<tx_dropped>[0-9]+)\s+overruns\s+"
r"(?P<tx_overruns>[0-9]+)\s+carrier\s+(?P<tx_carrier>[0-9]+)\s+collisions\s+(?P<tx_collisions>[0-9]+)",
re.I)
re_openbsd = [re_openbsd_interface, re_openbsd_ipv4, re_openbsd_ipv6, re_openbsd_details, re_openbsd_rx,
re_openbsd_rx_stats, re_openbsd_tx, re_openbsd_tx_stats]
# FreeBSD syntax
re_freebsd_interface = re.compile(
r"(?P<name>[a-zA-Z0-9:._-]+):\s+flags=(?P<flags>[0-9]+)<(?P<state>\S+)>\s+metric\s+"
r"(?P<metric>[0-9]+)\s+mtu\s+(?P<mtu>[0-9]+)",
re.I)
re_freebsd_ipv4 = re.compile(
r"inet (?P<ipv4_addr>(?:[0-9]{1,3}\.){3}[0-9]{1,3})\s+netmask\s+(?P<ipv4_mask>0x\S+)(\s+broadcast\s+"
r"(?P<ipv4_bcast>(?:[0-9]{1,3}\.){3}[0-9]{1,3}))?",
re.I)
re_freebsd_ipv6 = re.compile(r"\s?inet6\s(?P<ipv6_addr>.*)(?:\%\w+\d+)\sprefixlen\s(?P<ipv6_mask>\d+)(?:\s\w+)?\sscopeid\s(?P<ipv6_scope>\w+x\w+)", re.I)
re_freebsd_details = re.compile(r"ether\s+(?P<mac_addr>[0-9A-Fa-f:?]+)", re.I)
re_freebsd = [re_freebsd_interface, re_freebsd_ipv4, re_freebsd_ipv6, re_freebsd_details]
available_interfaces = dict()
for pattern in [re_linux_interface, re_openbsd_interface, re_freebsd_interface]:
network_interfaces = re.finditer(pattern, source_data)
positions = []
while True:
try:
pos = next(network_interfaces)
positions.append(max(pos.start() - 1, 0))
except StopIteration:
break
if positions:
positions.append(len(source_data))
break
if not positions:
return available_interfaces
for l, r in zip(positions, positions[1:]):
chunk = source_data[l:r]
_interface = dict()
for pattern in re_linux + re_openbsd + re_freebsd:
match = re.search(pattern, chunk.replace('\t', '\n'))
if match:
details = match.groupdict()
for k, v in details.items():
if isinstance(v, str): details[k] = v.strip()
_interface.update(details)
if _interface is not None:
available_interfaces[_interface['name']] = self.update_interface_details(_interface)
return available_interfaces
@staticmethod
def update_interface_details(interface):
for attr in IfconfigParser.attributes:
if attr not in interface:
interface[attr] = None
return namedtuple('Interface', interface.keys())(**interface)
class InterfaceNotFound(Exception):
"""
"""
pass
def process(proc_data):
"""
Final processing to conform to the schema.
@@ -220,7 +437,7 @@ def process(proc_data):
# convert OSX-style subnet mask to dotted quad
if 'ipv4_mask' in entry:
try:
if entry['ipv4_mask'].find('0x') == 0:
if entry['ipv4_mask'].startswith('0x'):
new_mask = entry['ipv4_mask']
new_mask = new_mask.lstrip('0x')
new_mask = '.'.join(str(int(i, 16)) for i in [new_mask[i:i + 2] for i in range(0, len(new_mask), 2)])

View File

@@ -134,7 +134,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.2'
description = 'iptables command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -247,7 +247,7 @@ def parse(data, raw=False, quiet=False):
for line in cleandata:
if line.find('Chain') == 0:
if line.startswith('Chain'):
raw_output.append(chain)
chain = {}
headers = []
@@ -259,7 +259,7 @@ def parse(data, raw=False, quiet=False):
continue
elif line.find('target') == 0 or line.find('pkts') == 1 or line.find('num') == 0:
elif line.startswith('target') or line.find('pkts') == 1 or line.startswith('num'):
headers = []
headers = [h for h in ' '.join(line.lower().strip().split()).split() if h]
headers.append("options")

View File

@@ -77,7 +77,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'jobs command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -176,11 +176,11 @@ def parse(data, raw=False, quiet=False):
parsed_line.insert(0, job_number)
# check for + or - in first field
if parsed_line[0].find('+') != -1:
if '+' in parsed_line[0]:
job_history = 'current'
parsed_line[0] = parsed_line[0].rstrip('+')
if parsed_line[0].find('-') != -1:
if '-' in parsed_line[0]:
job_history = 'previous'
parsed_line[0] = parsed_line[0].rstrip('-')

View File

@@ -72,7 +72,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'last and lastb command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -122,6 +122,9 @@ def process(proc_data):
if 'logout' in entry and entry['logout'] == 'still_logged_in':
entry['logout'] = 'still logged in'
if 'logout' in entry and entry['logout'] == 'gone_-_no_logout':
entry['logout'] = 'gone - no logout'
return proc_data
@@ -157,6 +160,7 @@ def parse(data, raw=False, quiet=False):
entry = entry.replace('system boot', 'system_boot')
entry = entry.replace(' still logged in', '- still_logged_in')
entry = entry.replace(' gone - no logout', '- gone_-_no_logout')
linedata = entry.split()
if re.match(r'[MTWFS][ouerha][nedritnu] [JFMASOND][aepuco][nbrynlgptvc]', ' '.join(linedata[2:4])):

View File

@@ -149,7 +149,7 @@ import jc.utils
class info():
version = '1.3'
version = '1.4'
description = 'ls command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -235,8 +235,9 @@ def parse(data, raw=False, quiet=False):
if not re.match(r'[-dclpsbDCMnP?]([-r][-w][-xsS]){2}([-r][-w][-xtT])[+]?', linedata[0]) \
and linedata[0].endswith(':'):
parent = linedata.pop(0)[:-1]
# Pop following total line
linedata.pop(0)
# Pop following total line if it exists
if re.match(r'total [0-9]+', linedata[0]):
linedata.pop(0)
if linedata:
# Check if -l was used to parse extra data

View File

@@ -97,7 +97,7 @@ import jc.parsers.universal
class info():
version = '1.0'
version = '1.1'
description = 'lsof command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -180,47 +180,6 @@ def parse(data, raw=False, quiet=False):
raw_output = jc.parsers.universal.sparse_table_parse(cleandata)
'''
# find column value of last character of each header
header_text = cleandata.pop(0).lower()
# clean up 'size/off' header
# even though forward slash in a key is valid json, it can make things difficult
header_row = header_text.replace('/', '_')
headers = header_row.split()
header_spec = []
for i, h in enumerate(headers):
# header tuple is (index, header_name, col)
header_spec.append((i, h, header_row.find(h) + len(h)))
# parse lines
for entry in cleandata:
output_line = {}
# normalize data by inserting Null for missing data
temp_line = entry.split(maxsplit=len(headers) - 1)
for spec in header_spec:
index = spec[0]
header_name = spec[1]
col = spec[2] - 1 # subtract one since column starts at 0 instead of 1
if header_name == 'command' or header_name == 'name':
continue
if entry[col] in string.whitespace:
temp_line.insert(index, None)
name = ' '.join(temp_line[9:])
fixed_line = temp_line[0:9]
fixed_line.append(name)
output_line = dict(zip(headers, fixed_line))
raw_output.append(output_line)
'''
if raw:
return raw_output
else:

View File

@@ -56,7 +56,7 @@ import jc.utils
class info():
version = '1.1'
version = '1.2'
description = 'mount command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -165,7 +165,7 @@ def parse(data, raw=False, quiet=False):
if cleandata:
# check for OSX output
if cleandata[0].find(' type ') == -1:
if ' type ' not in cleandata[0]:
raw_output = osx_parse(cleandata)
else:

View File

@@ -313,7 +313,7 @@ import jc.utils
class info():
version = '1.2'
version = '1.4'
description = 'netstat command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -498,14 +498,14 @@ def parse_post(raw_data):
if 'proto' in entry and 'kind' in entry:
if entry['kind'] == 'network':
if entry['proto'].find('tcp') != -1:
if 'tcp' in entry['proto']:
entry['transport_protocol'] = 'tcp'
elif entry['proto'].find('udp') != -1:
elif 'udp' in entry['proto']:
entry['transport_protocol'] = 'udp'
else:
entry['transport_protocol'] = None
if entry['proto'].find('6') != -1:
if '6' in entry['proto']:
entry['network_protocol'] = 'ipv6'
else:
entry['network_protocol'] = 'ipv4'
@@ -536,25 +536,34 @@ def parse(data, raw=False, quiet=False):
raw_output = []
network = False
socket = False
bluetooth = False
headers = ''
network_list = []
socket_list = []
for line in cleandata:
if line.find('Active Internet') == 0:
if line.startswith('Active Internet'):
network_list = []
network = True
socket = False
bluetooth = False
continue
if line.find('Active UNIX') == 0:
if line.startswith('Active UNIX'):
socket_list = []
network = False
socket = True
bluetooth = False
continue
if line.find('Proto') == 0:
if line.startswith('Active Bluetooth'):
network = False
socket = False
bluetooth = True
continue
if line.startswith('Proto'):
header_text = normalize_headers(line)
headers = header_text.split()
continue
@@ -567,6 +576,10 @@ def parse(data, raw=False, quiet=False):
socket_list.append(parse_socket(header_text, headers, line))
continue
if bluetooth:
# maybe implement later if requested
continue
for item in [network_list, socket_list]:
for entry in item:
raw_output.append(entry)

View File

@@ -32,7 +32,7 @@ import jc.parsers.universal
class info():
version = '1.0'
version = '1.1'
description = 'pip list command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -94,7 +94,7 @@ def parse(data, raw=False, quiet=False):
cleandata = list(filter(None, linedata))
# detect legacy output type
if cleandata[0].find(' (') != -1:
if ' (' in cleandata[0]:
for row in cleandata:
raw_output.append({'package': row.split(' (')[0],
'version': row.split(' (')[1].rstrip(')')})
@@ -103,7 +103,7 @@ def parse(data, raw=False, quiet=False):
else:
# clear separator line
for i, line in reversed(list(enumerate(cleandata))):
if line.find('---') != -1:
if '---' in line:
cleandata.pop(i)
cleandata[0] = cleandata[0].lower()

View File

@@ -104,7 +104,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'stat command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -208,7 +208,7 @@ def parse(data, raw=False, quiet=False):
output_line['file'] = line_list[1]
# populate link_to field if -> found
if output_line['file'].find(' -> ') != -1:
if ' -> ' in output_line['file']:
filename = output_line['file'].split(' -> ')[0].strip('\u2018').rstrip('\u2019')
link = output_line['file'].split(' -> ')[1].strip('\u2018').rstrip('\u2019')
output_line['file'] = filename
@@ -229,7 +229,7 @@ def parse(data, raw=False, quiet=False):
continue
# line #3
if line.find('Device:') == 0:
if line.startswith('Device:'):
line_list = line.split()
output_line['device'] = line_list[1]
output_line['inode'] = line_list[3]
@@ -237,7 +237,7 @@ def parse(data, raw=False, quiet=False):
continue
# line #4
if line.find('Access: (') == 0:
if line.startswith('Access: ('):
line = line.replace('(', ' ').replace(')', ' ').replace('/', ' ')
line_list = line.split()
output_line['access'] = line_list[1]
@@ -249,19 +249,19 @@ def parse(data, raw=False, quiet=False):
continue
# line #5
if line.find('Access: 2') == 0:
if line.startswith('Access: 2'):
line_list = line.split(maxsplit=1)
output_line['access_time'] = line_list[1]
continue
# line #6
if line.find('Modify:') == 0:
if line.startswith('Modify:'):
line_list = line.split(maxsplit=1)
output_line['modify_time'] = line_list[1]
continue
# line #7
if line.find('Change:') == 0:
if line.startswith('Change:'):
line_list = line.split(maxsplit=1)
output_line['change_time'] = line_list[1]
continue

View File

@@ -40,7 +40,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'systemctl command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -110,7 +110,7 @@ def parse(data, raw=False, quiet=False):
raw_output = []
for entry in cleandata[1:]:
if entry.find('LOAD = ') != -1:
if 'LOAD = ' in entry:
break
else:

View File

@@ -59,7 +59,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'systemctl list-jobs command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -137,7 +137,7 @@ def parse(data, raw=False, quiet=False):
raw_output = []
for entry in cleandata[1:]:
if entry.find('No jobs running.') != -1 or entry.find('jobs listed.') != -1:
if 'No jobs running.' in entry or 'jobs listed.' in entry:
break
else:

View File

@@ -34,7 +34,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'systemctl list-sockets command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -102,7 +102,7 @@ def parse(data, raw=False, quiet=False):
raw_output = []
for entry in cleandata[1:]:
if entry.find('sockets listed.') != -1:
if 'sockets listed.' in entry:
break
else:

View File

@@ -31,7 +31,7 @@ import jc.utils
class info():
version = '1.0'
version = '1.1'
description = 'systemctl list-unit-files command parser'
author = 'Kelly Brazil'
author_email = 'kellyjonbrazil@gmail.com'
@@ -99,7 +99,7 @@ def parse(data, raw=False, quiet=False):
raw_output = []
for entry in cleandata[1:]:
if entry.find('unit files listed.') != -1:
if 'unit files listed.' in entry:
break
else:

View File

@@ -1,3 +1,3 @@
ifconfig-parser>=0.0.5
ruamel.yaml>=0.15.0
xmltodict>=0.12.0
Pygments>=2.4.2

2
setup.cfg Normal file
View File

@@ -0,0 +1,2 @@
[metadata]
license_file = LICENSE.md

View File

@@ -5,22 +5,21 @@ with open('README.md', 'r') as f:
setuptools.setup(
name='jc',
version='1.9.3',
version='1.10.12',
author='Kelly Brazil',
author_email='kellyjonbrazil@gmail.com',
description='This tool serializes the output of popular command line tools and filetypes to structured JSON output.',
install_requires=[
'ifconfig-parser>=0.0.5',
'ruamel.yaml>=0.15.0',
'xmltodict>=0.12.0'
'xmltodict>=0.12.0',
'Pygments>=2.4.2'
],
license='MIT',
long_description=long_description,
long_description_content_type='text/markdown',
python_requires='>=3.6',
url='https://github.com/kellyjonbrazil/jc',
packages=setuptools.find_packages(),
include_package_data=True,
packages=setuptools.find_packages(exclude=['*.tests', '*.tests.*', 'tests.*', 'tests']),
entry_points={
'console_scripts': [
'jc=jc.cli:main'

View File

@@ -1 +1 @@
[{"filename": "systemd-private-016de60725a3426792b93fc9f120b8f0-chronyd.service-oZqq4u", "parent": "."}, {"filename": "systemd-private-a30a5a178daa4042b42dfaf5ff9e5f68-chronyd.service-a1tpxv", "parent": "."}, {"filename": "systemd-private-af69d7360f3e40cfa947358c0fb5a6f8-chronyd.service-T3MQ4j", "parent": "."}, {"filename": "tmp.CvALl2jE6u", "parent": "."}, {"filename": "tmp.e7AlxSxY5a", "parent": "."}, {"filename": "tmp.uXm9yegjwj", "parent": "."}, {"filename": "a regular filename", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "a combination", "parent": "./lstest"}, {"filename": "of everything", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "a newline inside", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "four contiguous newlines inside", "parent": "./lstest"}, {"filename": "this file", "parent": "./lstest"}, {"filename": "has", "parent": "./lstest"}, {"filename": "six", "parent": "./lstest"}, {"filename": "newlines", "parent": "./lstest"}, {"filename": "within", "parent": "./lstest"}, {"filename": "this file starts with four newlines", "parent": "./lstest"}, {"filename": "this file starts with one newline", "parent": "./lstest"}]
[{"filename": "lstest", "parent": "."}, {"filename": "systemd-private-016de60725a3426792b93fc9f120b8f0-chronyd.service-oZqq4u", "parent": "."}, {"filename": "systemd-private-a30a5a178daa4042b42dfaf5ff9e5f68-chronyd.service-a1tpxv", "parent": "."}, {"filename": "systemd-private-af69d7360f3e40cfa947358c0fb5a6f8-chronyd.service-T3MQ4j", "parent": "."}, {"filename": "tmp.CvALl2jE6u", "parent": "."}, {"filename": "tmp.e7AlxSxY5a", "parent": "."}, {"filename": "tmp.uXm9yegjwj", "parent": "."}, {"filename": "a regular filename", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "a combination", "parent": "./lstest"}, {"filename": "of everything", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "a newline inside", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "four contiguous newlines inside", "parent": "./lstest"}, {"filename": "this file", "parent": "./lstest"}, {"filename": "has", "parent": "./lstest"}, {"filename": "six", "parent": "./lstest"}, {"filename": "newlines", "parent": "./lstest"}, {"filename": "within", "parent": "./lstest"}, {"filename": "this file starts with four newlines", "parent": "./lstest"}, {"filename": "this file starts with one newline", "parent": "./lstest"}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
tests/fixtures/fedora32/last.json vendored Normal file
View File

@@ -0,0 +1 @@
[{"user": "kbrazil", "tty": "pts/0", "hostname": "192.168.71.1", "login": "Fri May 1 15:25", "logout": "gone - no logout"}, {"user": "kbrazil", "tty": "tty1", "hostname": null, "login": "Fri May 1 15:24", "logout": "gone - no logout"}, {"user": "reboot", "tty": "system boot", "hostname": "5.6.6-300.fc32.x", "login": "Fri May 1 15:24", "logout": "running"}, {"user": "kbrazil", "tty": "pts/0", "hostname": "192.168.71.1", "login": "Thu Apr 30 15:27", "logout": "15:22", "duration": "23:55"}, {"user": "kbrazil", "tty": "tty1", "hostname": null, "login": "Thu Apr 30 15:25", "logout": "down", "duration": "23:57"}, {"user": "reboot", "tty": "system boot", "hostname": "5.6.6-300.fc32.x", "login": "Thu Apr 30 15:22", "logout": "15:22", "duration": "1+00:00"}]

8
tests/fixtures/fedora32/last.out vendored Normal file
View File

@@ -0,0 +1,8 @@
kbrazil pts/0 192.168.71.1 Fri May 1 15:25 gone - no logout
kbrazil tty1 Fri May 1 15:24 gone - no logout
reboot system boot 5.6.6-300.fc32.x Fri May 1 15:24 still running
kbrazil pts/0 192.168.71.1 Thu Apr 30 15:27 - 15:22 (23:55)
kbrazil tty1 Thu Apr 30 15:25 - down (23:57)
reboot system boot 5.6.6-300.fc32.x Thu Apr 30 15:22 - 15:22 (1+00:00)
wtmp begins Thu Apr 30 15:22:02 2020

1
tests/fixtures/fedora32/netstat.json vendored Normal file

File diff suppressed because one or more lines are too long

139
tests/fixtures/fedora32/netstat.out vendored Normal file
View File

@@ -0,0 +1,139 @@
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 localhost.localdoma:ssh 192.168.71.1:52882 ESTABLISHED
udp 0 0 localhost.locald:bootpc 192.168.71.254:bootps ESTABLISHED
Active UNIX domain sockets (w/o servers)
Proto RefCnt Flags Type State I-Node Path
unix 2 [ ] DGRAM 36371 /run/user/1000/systemd/notify
unix 3 [ ] DGRAM 15453 /run/systemd/notify
unix 9 [ ] DGRAM 15471 /run/systemd/journal/dev-log
unix 2 [ ] DGRAM 165956 @userdb-9bf8f59ca8f2bcce47a377edbaf8985d
unix 12 [ ] DGRAM 15479 /run/systemd/journal/socket
unix 2 [ ] DGRAM 29064 /var/run/chrony/chronyd.sock
unix 2 [ ] DGRAM 165866 @userdb-3f208fa7c8c6d98822a696ee7ca5e3ad
unix 2 [ ] DGRAM 165863 @userdb-da253950013f5ea3bc51a4049480d04e
unix 2 [ ] DGRAM 165865
unix 3 [ ] STREAM CONNECTED 36476
unix 3 [ ] STREAM CONNECTED 31389
unix 3 [ ] STREAM CONNECTED 30999 /var/lib/sss/pipes/private/sbus-dp_implicit_files.692
unix 3 [ ] STREAM CONNECTED 33770 /run/dbus/system_bus_socket
unix 3 [ ] STREAM CONNECTED 30731
unix 2 [ ] DGRAM 24653
unix 3 [ ] STREAM CONNECTED 31370 /var/lib/sss/pipes/private/sbus-dp_implicit_files.692
unix 2 [ ] DGRAM 165862
unix 2 [ ] DGRAM 33725
unix 3 [ ] STREAM CONNECTED 33885
unix 3 [ ] STREAM CONNECTED 31173
unix 3 [ ] STREAM CONNECTED 31019
unix 3 [ ] STREAM CONNECTED 33886
unix 3 [ ] STREAM CONNECTED 30735 /var/lib/sss/pipes/private/sbus-monitor
unix 3 [ ] STREAM CONNECTED 166231
unix 2 [ ] DGRAM 30987
unix 2 [ ] DGRAM 24745
unix 2 [ ] DGRAM 165955
unix 2 [ ] DGRAM 30575
unix 3 [ ] STREAM CONNECTED 31062 /run/dbus/system_bus_socket
unix 2 [ ] STREAM CONNECTED 35419
unix 3 [ ] STREAM CONNECTED 31399 /run/dbus/system_bus_socket
unix 2 [ ] DGRAM 32430
unix 2 [ ] DGRAM 29403
unix 3 [ ] STREAM CONNECTED 166232 /var/lib/sss/pipes/nss
unix 2 [ ] DGRAM 31127
unix 2 [ ] DGRAM 29538
unix 2 [ ] STREAM CONNECTED 33876
unix 3 [ ] STREAM CONNECTED 31020 /var/lib/sss/pipes/private/sbus-monitor
unix 3 [ ] STREAM CONNECTED 30998
unix 3 [ ] STREAM CONNECTED 36475
unix 2 [ ] DGRAM 36002
unix 3 [ ] STREAM CONNECTED 33769
unix 3 [ ] STREAM CONNECTED 31390 /var/lib/sss/pipes/private/sbus-monitor
unix 2 [ ] STREAM CONNECTED 33132
unix 3 [ ] STREAM CONNECTED 31398
unix 3 [ ] STREAM CONNECTED 30022
unix 3 [ ] DGRAM 24748
unix 3 [ ] DGRAM 24747
unix 2 [ ] DGRAM 36348
unix 2 [ ] DGRAM 32085
unix 3 [ ] STREAM CONNECTED 31168 /run/systemd/journal/stdout
unix 3 [ ] DGRAM 15455
unix 3 [ ] STREAM CONNECTED 30830
unix 3 [ ] STREAM CONNECTED 26911
unix 3 [ ] STREAM CONNECTED 35960
unix 2 [ ] DGRAM 24662
unix 2 [ ] STREAM CONNECTED 36345
unix 3 [ ] STREAM CONNECTED 32538
unix 3 [ ] STREAM CONNECTED 28747
unix 3 [ ] STREAM CONNECTED 31026
unix 3 [ ] DGRAM 28615
unix 3 [ ] STREAM CONNECTED 31713 /run/dbus/system_bus_socket
unix 3 [ ] STREAM CONNECTED 31167
unix 3 [ ] STREAM CONNECTED 35989 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 36303 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 31809 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 29396 /run/systemd/journal/stdout
unix 2 [ ] STREAM CONNECTED 34911
unix 3 [ ] DGRAM 36374
unix 2 [ ] STREAM CONNECTED 32241
unix 3 [ ] STREAM CONNECTED 32057 /run/dbus/system_bus_socket
unix 3 [ ] STREAM CONNECTED 32000 /run/systemd/journal/stdout
unix 2 [ ] DGRAM 33083
unix 3 [ ] STREAM CONNECTED 31017
unix 3 [ ] DGRAM 36373
unix 3 [ ] STREAM CONNECTED 34101
unix 3 [ ] STREAM CONNECTED 32539 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 26914
unix 3 [ ] STREAM CONNECTED 29395
unix 3 [ ] STREAM CONNECTED 31022
unix 3 [ ] DGRAM 15456
unix 3 [ ] STREAM CONNECTED 29251 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 26915
unix 2 [ ] STREAM CONNECTED 33052
unix 3 [ ] STREAM CONNECTED 31999
unix 2 [ ] DGRAM 29496
unix 3 [ ] STREAM CONNECTED 28400
unix 3 [ ] STREAM CONNECTED 36378 /run/dbus/system_bus_socket
unix 3 [ ] STREAM CONNECTED 31808
unix 3 [ ] STREAM CONNECTED 29250
unix 3 [ ] STREAM CONNECTED 24582
unix 3 [ ] STREAM CONNECTED 32056
unix 3 [ ] STREAM CONNECTED 31347
unix 3 [ ] STREAM CONNECTED 28678 /run/systemd/journal/stdout
unix 2 [ ] DGRAM 30980
unix 3 [ ] STREAM CONNECTED 31348 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 28677
unix 3 [ ] STREAM CONNECTED 34102 /run/dbus/system_bus_socket
unix 3 [ ] STREAM CONNECTED 24805 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 29322 /run/systemd/journal/stdout
unix 2 [ ] DGRAM 36357
unix 3 [ ] STREAM CONNECTED 30832 /run/systemd/journal/stdout
unix 2 [ ] DGRAM 26913
unix 2 [ ] STREAM CONNECTED 32010
unix 3 [ ] STREAM CONNECTED 28748 /run/systemd/journal/stdout
unix 2 [ ] DGRAM 28602
unix 3 [ ] STREAM CONNECTED 28401 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 31069 /run/dbus/system_bus_socket
unix 3 [ ] DGRAM 28616
unix 3 [ ] STREAM CONNECTED 32334 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 31712
unix 3 [ ] STREAM CONNECTED 36302
unix 3 [ ] STREAM CONNECTED 32333
unix 3 [ ] STREAM CONNECTED 32093
unix 3 [ ] STREAM CONNECTED 26912
unix 2 [ ] STREAM CONNECTED 35774
unix 3 [ ] STREAM CONNECTED 29321
unix 3 [ ] STREAM CONNECTED 31025
unix 3 [ ] STREAM CONNECTED 28470
unix 3 [ ] STREAM CONNECTED 32094 /run/dbus/system_bus_socket
unix 3 [ ] STREAM CONNECTED 31259 /run/systemd/journal/stdout
unix 2 [ ] DGRAM 36471
unix 3 [ ] STREAM CONNECTED 31054
unix 3 [ ] STREAM CONNECTED 31064 /run/dbus/system_bus_socket
unix 3 [ ] STREAM CONNECTED 28471 /run/systemd/journal/stdout
unix 3 [ ] STREAM CONNECTED 36377
unix 2 [ ] STREAM CONNECTED 35788
unix 3 [ ] STREAM CONNECTED 31258
unix 3 [ ] STREAM CONNECTED 31065 /run/dbus/system_bus_socket
Active Bluetooth connections (w/o servers)
Proto Destination Source State PSM DCID SCID IMTU OMTU Security
Proto Destination Source State Channel

File diff suppressed because one or more lines are too long

View File

@@ -911,13 +911,6 @@
112 /usr/local/Homebrew/Library/Homebrew/rubocops/cask
40 /usr/local/Homebrew/Library/Homebrew/rubocops/extend
344 /usr/local/Homebrew/Library/Homebrew/rubocops
288 /usr/local/Homebrew/Library/Homebrew/shims/linux/super
288 /usr/local/Homebrew/Library/Homebrew/shims/linux
344 /usr/local/Homebrew/Library/Homebrew/shims/mac/super
344 /usr/local/Homebrew/Library/Homebrew/shims/mac
16 /usr/local/Homebrew/Library/Homebrew/shims/scm
280 /usr/local/Homebrew/Library/Homebrew/shims/super
928 /usr/local/Homebrew/Library/Homebrew/shims
24 /usr/local/Homebrew/Library/Homebrew/test/cask/artifact/shared_examples
144 /usr/local/Homebrew/Library/Homebrew/test/cask/artifact
24 /usr/local/Homebrew/Library/Homebrew/test/cask/cask_loader

File diff suppressed because one or more lines are too long

View File

@@ -317,13 +317,6 @@
32 /usr/local/Homebrew/Library/Homebrew/cli
56 /usr/local/Homebrew/Library/Homebrew/manpages
8 /usr/local/Homebrew/Library/Homebrew/version
64 /usr/local/Homebrew/Library/Homebrew/shims/mac/super
64 /usr/local/Homebrew/Library/Homebrew/shims/mac
24 /usr/local/Homebrew/Library/Homebrew/shims/super
8 /usr/local/Homebrew/Library/Homebrew/shims/linux/super
8 /usr/local/Homebrew/Library/Homebrew/shims/linux
8 /usr/local/Homebrew/Library/Homebrew/shims/scm
104 /usr/local/Homebrew/Library/Homebrew/shims
8 /usr/local/Homebrew/Library/Homebrew/debrew
56 /usr/local/Homebrew/Library/Homebrew/os/mac/pkgconfig/10.8
24 /usr/local/Homebrew/Library/Homebrew/os/mac/pkgconfig/10.6

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
[{"filename": "systemd-private-65c1089f1d4c4cf5bc50ca55478abfde-systemd-resolved.service-La9AqY", "parent": "."}, {"filename": "systemd-private-65c1089f1d4c4cf5bc50ca55478abfde-systemd-timesyncd.service-L7q4cJ", "parent": "."}, {"filename": "vmware-root_670-2722828838", "parent": "."}, {"filename": "a regular filename", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "a combination", "parent": "./lstest"}, {"filename": "of everything", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "a newline inside", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "four contiguous newlines inside", "parent": "./lstest"}, {"filename": "this file", "parent": "./lstest"}, {"filename": "has", "parent": "./lstest"}, {"filename": "six", "parent": "./lstest"}, {"filename": "newlines", "parent": "./lstest"}, {"filename": "within", "parent": "./lstest"}, {"filename": "this file starts with four newlines", "parent": "./lstest"}, {"filename": "this file starts with one newline", "parent": "./lstest"}]
[{"filename": "lstest", "parent": "."}, {"filename": "systemd-private-65c1089f1d4c4cf5bc50ca55478abfde-systemd-resolved.service-La9AqY", "parent": "."}, {"filename": "systemd-private-65c1089f1d4c4cf5bc50ca55478abfde-systemd-timesyncd.service-L7q4cJ", "parent": "."}, {"filename": "vmware-root_670-2722828838", "parent": "."}, {"filename": "a regular filename", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "a combination", "parent": "./lstest"}, {"filename": "of everything", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "a newline inside", "parent": "./lstest"}, {"filename": "this file has", "parent": "./lstest"}, {"filename": "four contiguous newlines inside", "parent": "./lstest"}, {"filename": "this file", "parent": "./lstest"}, {"filename": "has", "parent": "./lstest"}, {"filename": "six", "parent": "./lstest"}, {"filename": "newlines", "parent": "./lstest"}, {"filename": "within", "parent": "./lstest"}, {"filename": "this file starts with four newlines", "parent": "./lstest"}, {"filename": "this file starts with one newline", "parent": "./lstest"}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -31,6 +31,9 @@ class MyTests(unittest.TestCase):
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/last-w.out'), 'r', encoding='utf-8') as f:
self.ubuntu_18_4_last_w = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/fedora32/last.out'), 'r', encoding='utf-8') as f:
self.fedora32_last = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/last.json'), 'r', encoding='utf-8') as f:
self.centos_7_7_last_json = json.loads(f.read())
@@ -53,6 +56,9 @@ class MyTests(unittest.TestCase):
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/last-w.json'), 'r', encoding='utf-8') as f:
self.ubuntu_18_4_last_w_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/fedora32/last.json'), 'r', encoding='utf-8') as f:
self.fedora32_last_json = json.loads(f.read())
def test_last_centos_7_7(self):
"""
Test plain 'last' on Centos 7.7
@@ -95,6 +101,12 @@ class MyTests(unittest.TestCase):
"""
self.assertEqual(jc.parsers.last.parse(self.ubuntu_18_4_last_w, quiet=True), self.ubuntu_18_4_last_w_json)
def test_last_fedora32(self):
"""
Test plain 'last' on Fedora32
"""
self.assertEqual(jc.parsers.last.parse(self.fedora32_last, quiet=True), self.fedora32_last_json)
if __name__ == '__main__':
unittest.main()

View File

@@ -40,6 +40,9 @@ class MyTests(unittest.TestCase):
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/netstat-sudo-aeep.out'), 'r', encoding='utf-8') as f:
self.ubuntu_18_4_netstat_sudo_aeep = f.read()
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/fedora32/netstat.out'), 'r', encoding='utf-8') as f:
self.fedora32_netstat = f.read()
# output
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/centos-7.7/netstat.json'), 'r', encoding='utf-8') as f:
self.centos_7_7_netstat_json = json.loads(f.read())
@@ -71,6 +74,9 @@ class MyTests(unittest.TestCase):
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/ubuntu-18.04/netstat-sudo-aeep.json'), 'r', encoding='utf-8') as f:
self.ubuntu_18_4_netstat_sudo_aeep_json = json.loads(f.read())
with open(os.path.join(THIS_DIR, os.pardir, 'tests/fixtures/fedora32/netstat.json'), 'r', encoding='utf-8') as f:
self.fedora32_netstat_json = json.loads(f.read())
def test_netstat_centos_7_7(self):
"""
Test 'netstat' on Centos 7.7
@@ -131,6 +137,12 @@ class MyTests(unittest.TestCase):
"""
self.assertEqual(jc.parsers.netstat.parse(self.ubuntu_18_4_netstat_sudo_aeep, quiet=True), self.ubuntu_18_4_netstat_sudo_aeep_json)
def test_netstat_fedora32(self):
"""
Test 'netstat' on Fedora32
"""
self.assertEqual(jc.parsers.netstat.parse(self.fedora32_netstat, quiet=True), self.fedora32_netstat_json)
if __name__ == '__main__':
unittest.main()