diff --git a/README.md b/README.md index 9789fa65..f16c3403 100644 --- a/README.md +++ b/README.md @@ -533,20 +533,22 @@ for item in result: print(item["filename"]) ``` -### Custom Parsers -Custom local parser plugins may be placed in a `jc/jcparsers` folder in your -local **"App data directory"**: +### Parser Plugins +Parser plugins may be placed in a `jc/jcparsers` folder in your local +**"App data directory"**: - Linux/unix: `$HOME/.local/share/jc/jcparsers` - macOS: `$HOME/Library/Application Support/jc/jcparsers` - Windows: `$LOCALAPPDATA\jc\jc\jcparsers` -Local parser plugins are standard python module files. Use the +Parser plugins are standard python module files. Use the [`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py) 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. +Any dependencies can be placed in the `jc` folder above `jcparsers` and can +be imported in the parser code. -Local plugin filenames must be valid python module names and therefore must +Parser plugin filenames must be valid python module names and therefore must start with a letter and consist entirely of alphanumerics and underscores. Local plugins may override default parsers. diff --git a/jc/cli.py b/jc/cli.py index 66b808c9..dd175b59 100644 --- a/jc/cli.py +++ b/jc/cli.py @@ -215,14 +215,14 @@ class JcCli(): category_text: str = '' padding_char: str = ' ' all_parsers = all_parser_info(show_hidden=True, show_deprecated=False) - generic = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'generic' in x['tags']] - standard = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'standard' in x['tags']] - command = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'command' in x['tags']] + generic = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'generic' in x.get('tags', [])] + standard = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'standard' in x.get('tags', [])] + command = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if 'command' in x.get('tags', [])] file_str_bin = [ {'arg': x['argument'], 'desc': x['description']} for x in all_parsers - if 'file' in x['tags'] or - 'string' in x['tags'] or - 'binary' in x['tags'] + if 'file' in x.get('tags', []) or + 'string' in x.get('tags', []) or + 'binary' in x.get('tags', []) ] streaming = [{'arg': x['argument'], 'desc': x['description']} for x in all_parsers if x.get('streaming')] categories: Dict = { diff --git a/jc/lib.py b/jc/lib.py index 4621b907..54f23436 100644 --- a/jc/lib.py +++ b/jc/lib.py @@ -213,6 +213,19 @@ def _modname_to_cliname(parser_mod_name: str) -> str: """Return module's cli name (underscores converted to dashes)""" return parser_mod_name.replace('_', '-') +def _is_valid_parser_plugin(name: str, local_parsers_dir: str) -> bool: + if re.match(r'\w+\.py$', name) and os.path.isfile(os.path.join(local_parsers_dir, name)): + try: + parser_mod_name = _cliname_to_modname(name)[0:-3] + modpath = 'jcparsers.' + plugin = importlib.import_module(f'{modpath}{parser_mod_name}') + if hasattr(plugin, 'info') and hasattr(plugin, 'parse'): + del plugin + return True + except Exception: + return False + return False + # Create the local_parsers list. This is a list of custom or # override parsers from /jc/jcparsers/*.py. # Once this list is created, extend the parsers list with it. @@ -222,7 +235,7 @@ local_parsers_dir = os.path.join(data_dir, 'jcparsers') if os.path.isdir(local_parsers_dir): sys.path.append(data_dir) for name in os.listdir(local_parsers_dir): - if re.match(r'\w+\.py$', name) and os.path.isfile(os.path.join(local_parsers_dir, name)): + if _is_valid_parser_plugin(name, local_parsers_dir): plugin_name = name[0:-3] local_parsers.append(_modname_to_cliname(plugin_name)) if plugin_name not in parsers: diff --git a/man/jc.1 b/man/jc.1 index 5aa93e78..0dd56e5d 100644 --- a/man/jc.1 +++ b/man/jc.1 @@ -1,4 +1,4 @@ -.TH jc 1 2023-03-22 1.23.1 "JSON Convert" +.TH jc 1 2023-03-23 1.23.1 "JSON Convert" .SH NAME \fBjc\fP \- JSON Convert JSONifies the output of many CLI tools, file-types, and strings @@ -1316,8 +1316,8 @@ etc... Note: Unbuffered output can be slower for large data streams. .RE -.SH CUSTOM PARSERS -Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your +.SH PARSER PLUGINS +Parser plugins may be placed in a \fBjc/jcparsers\fP folder in your local "App data directory": .RS @@ -1328,11 +1328,13 @@ local "App data directory": .fi .RE -Local parser plugins are standard python module files. Use the +Parser plugins are standard python module files. Use the \fBjc/parsers/foo.py\fP or \fBjc/parsers/foo_s.py\fP (streaming) parser as a template and simply place a \fB.py\fP file in the \fBjcparsers\fP subfolder. +Any dependencies can be placed in the \fBjc\fP folder above \fBjcparsers\fP +and can be imported in the parser code. -Local plugin filenames must be valid python module names and therefore must +Parser plugin filenames must be valid python module names and therefore must start with a letter and consist entirely of alphanumerics and underscores. Local plugins may override default parsers. diff --git a/templates/manpage_template b/templates/manpage_template index 9630e8cb..26e8a0c2 100644 --- a/templates/manpage_template +++ b/templates/manpage_template @@ -366,8 +366,8 @@ etc... Note: Unbuffered output can be slower for large data streams. .RE -.SH CUSTOM PARSERS -Custom local parser plugins may be placed in a \fBjc/jcparsers\fP folder in your +.SH PARSER PLUGINS +Parser plugins may be placed in a \fBjc/jcparsers\fP folder in your local "App data directory": .RS @@ -378,11 +378,13 @@ local "App data directory": .fi .RE -Local parser plugins are standard python module files. Use the +Parser plugins are standard python module files. Use the \fBjc/parsers/foo.py\fP or \fBjc/parsers/foo_s.py\fP (streaming) parser as a template and simply place a \fB.py\fP file in the \fBjcparsers\fP subfolder. +Any dependencies can be placed in the \fBjc\fP folder above \fBjcparsers\fP +and can be imported in the parser code. -Local plugin filenames must be valid python module names and therefore must +Parser plugin filenames must be valid python module names and therefore must start with a letter and consist entirely of alphanumerics and underscores. Local plugins may override default parsers. diff --git a/templates/readme_template b/templates/readme_template index 16ca2d6a..92b767bf 100644 --- a/templates/readme_template +++ b/templates/readme_template @@ -393,20 +393,22 @@ for item in result: print(item["filename"]) ``` -### Custom Parsers -Custom local parser plugins may be placed in a `jc/jcparsers` folder in your -local **"App data directory"**: +### Parser Plugins +Parser plugins may be placed in a `jc/jcparsers` folder in your local +**"App data directory"**: - Linux/unix: `$HOME/.local/share/jc/jcparsers` - macOS: `$HOME/Library/Application Support/jc/jcparsers` - Windows: `$LOCALAPPDATA\jc\jc\jcparsers` -Local parser plugins are standard python module files. Use the +Parser plugins are standard python module files. Use the [`jc/parsers/foo.py`](https://github.com/kellyjonbrazil/jc/blob/master/jc/parsers/foo.py) 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. +Any dependencies can be placed in the `jc` folder above `jcparsers` and can +be imported in the parser code. -Local plugin filenames must be valid python module names and therefore must +Parser plugin filenames must be valid python module names and therefore must start with a letter and consist entirely of alphanumerics and underscores. Local plugins may override default parsers.