Skip to content

Commit

Permalink
Add basic name parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
silvncr committed Apr 22, 2024
1 parent 4bdf647 commit 0dc7a13
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 37 deletions.
36 changes: 19 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

simplifying args jargon

![[license](LICENSE)](https://img.shields.io/github/license/silvncr/jarguments)
![[publish status](https://github.com/silvncr/jarguments/actions/workflows/python-publish.yml)](https://img.shields.io/github/actions/workflow/status/silvncr/jarguments/python-publish.yml)
![[latest release](https://github.com/silvncr/jarguments/releases/latest)](https://img.shields.io/github/v/release/silvncr/jarguments)

Expand All @@ -24,9 +25,7 @@ Providing a straightforward way to create command-line arguments.
## Installation

```sh
git clone https://github.com/silvncr/jarguments.git
cd jarguments
python setup.py install
pip install jarguments
```

## Usage
Expand All @@ -38,50 +37,53 @@ There are three steps to using the jarguments library:
1. Import the jarguments library.

```py
from jarguments import *
import jarguments as j
```

2. Provide your arguments with jarguments' classes.

```py
args = JParser(
JBool('show_text', help='determines whether "text" is shown'),
JStr('text'),
args = j.JParser(
j.JBool('show-text', help='determines whether "text" is shown'),
j.JInt('number', default=1),
j.JArgument('text', type=str, multiple=True),
)
```

3. Use your outputs; they are parsed automatically.
3. Use the outputs; they're parsed automatically!

```py
if args.show_text:
print(args.text)
for _ in range(args.number):
print(args.text)
```

### Command-line

- Now you can run your script with arguments:

```sh
$ python script.py --show_text --text "hello world"
hello world
$ python example.py --show-text --text "hello" "world"
["hello", "world"]
```

- Arguments without a default value are required. If you don't provide them, the script will raise an error:

```sh
$ python script.py --show_text
$ python example.py --show-text
error: the following arguments are required: --text
```

- If you want to see help messages, run your script with the `-h` or `--help` flag:

```sh
$ python script.py -h
usage: script.py [-h] [--show_text] --text TEXT
$ python example.py -h
usage: example.py [-h] [--show-text [SHOW_TEXT]] [--number NUMBER] --text [TEXT ...]

options:
-h, --help show this help message and exit
--show_text [SHOW_TEXT]
-h, --help show this help message and exit
--show-text [SHOW_TEXT]
determines whether "text" is shown
--text TEXT
--number NUMBER
--text [TEXT ...]
```
179 changes: 164 additions & 15 deletions jarguments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,63 @@
Providing a straightforward way to create command-line arguments.
'''


# imports
from argparse import Action, ArgumentParser, Namespace
from typing import Any, Optional, Type, Union


# module info
__author__ = 'silvncr'
__license__ = 'MIT'
__module_name__ = 'jarguments'
__python_version__ = '3.6'
__version__ = '0.0.1'
__version__ = '0.1.0'


# parse argument name
def parse_argument_name(name: str) -> Optional[str]:
'''
Used to parse an argument name from a string.
'''
for i in ['--', '-', '/']:
if name.startswith(i):
name = name[len(i):]
_name = name = name.strip().lower().replace(' ', '-').replace('_', '-')
for char in _name:
if not char.isalnum() and char not in ['-', '_']:
name = name.replace(char, '')
return name or None

# base argument

# base argument class
class JArgument:
'''
Base argument class.
'''
def __init__(
self,
name: str,
type: Type[Union[bool, str]],
type: Type[Union[bool, int, str]],
default: Union[Any, Optional[bool]] = None,
short_name: Optional[str] = None,
multiple: Optional[bool] = False,
help: Optional[str] = None,
):
self.name = name
self.short_name = short_name or None
if not name:
raise ValueError(f'invalid argument name: "{name}"; \
must not be empty')
if not name[0].isalpha():
raise ValueError(f'invalid argument name: "{name}"; \
must start with a letter')
elif name:
if _name := parse_argument_name(name):
self.name = _name
self.python_name = _name.replace('-', '_')
else:
raise ValueError(f'invalid argument name: "{name}"; \
an error occured while processing the name')
self.short_name = parse_argument_name(short_name) if short_name else None
self.type = type
self.default = default
self.required = default is None
Expand All @@ -42,6 +74,9 @@ def __init__(

# boolean argument
class JBool(JArgument):
'''
Boolean argument class. Extends `JArgument`.
'''
def __init__(
self,
name: str,
Expand All @@ -59,8 +94,33 @@ def __init__(
)


# integer argument
class JInt(JArgument):
'''
Integer argument class. Extends `JArgument`.
'''
def __init__(
self,
name: str,
short_name: Optional[str] = None,
default: Optional[int] = None,
help: Optional[str] = None,
):
super().__init__(
name,
int,
default,
short_name or None,
False,
help,
)


# string argument
class JStr(JArgument):
'''
String argument class. Extends `JArgument`.
'''
def __init__(
self,
name: str,
Expand All @@ -78,8 +138,43 @@ def __init__(
)


# boolean argument parser
class JBooleanAction(Action):
# base argument action
class JAction(Action):
'''
Base argument parser.
'''
def __init__(
self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar=None,
):
super(JAction, self).__init__(
option_strings,
dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar,
)


# boolean argument action
class JBoolAction(JAction):
'''
Boolean argument parser. Extends `JAction`.
'''
def __init__(
self,
option_strings,
Expand All @@ -93,13 +188,13 @@ def __init__(
help=None,
metavar=None,
):
super(JBooleanAction, self).__init__(
super(JBoolAction, self).__init__(
option_strings,
dest,
nargs=nargs,
const=const,
const=const, # type: ignore
default=default,
type=type,
type=type, # type: ignore
choices=choices,
required=required,
help=help,
Expand All @@ -122,21 +217,56 @@ def __call__(
)


# base parser
# integer argument action
class JIntAction(JAction):
'''
Integer argument parser. Extends `JAction`.
'''
def __call__(self, parser, namespace, values, option_string=None):
try:
setattr(namespace, self.dest, int(str(values)))
except ValueError:
parser.error(f"Invalid integer value: {values}")


# string argument action
class JStrAction(JAction):
'''
String argument parser. Extends `JAction`.
'''
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, str(values))


# parser
class JParser(ArgumentParser):
'''
Parser for instances of `JArgument` and its extensions.
'''
def __init__(self, *args: JArgument):
super().__init__()
for arg in args:
args_dict = {
'name': (f'--{arg.name}',),
'default': arg.default,
'dest': arg.python_name,
'help': arg.help,
}
if arg.type == bool:
args_dict = {
**args_dict, **{
'action': JBooleanAction,
'dest': arg.name,
'action': JBoolAction,
}
}
elif arg.type == int:
args_dict = {
**args_dict, **{
'action': JIntAction,
}
}
elif arg.type == str:
args_dict = {
**args_dict, **{
'action': JStrAction,
}
}
else:
Expand All @@ -148,9 +278,28 @@ def __init__(self, *args: JArgument):
}
}
if arg.short_name:
args_dict['name'] = (f'--{arg.name}', f'-{arg.short_name}')
args_dict = {
**args_dict, **{
'name': (f'--{arg.name}', f'-{arg.short_name}'),
}
}
else:
args_dict = {
**args_dict, **{
'name': (f'--{arg.name}',),
}
}
self.add_argument(*args_dict.pop('name'), **args_dict)

# parse arguments
def parse_args(self, *args, **kwargs) -> Namespace:
namespace = super().parse_args(*args, **kwargs)
args_dict = vars(namespace)
args_obj = Namespace()
for python_name, value in args_dict.items():
setattr(args_obj, python_name, value)
return args_obj

# return parsed arguments
def __new__(cls, *args: JArgument) -> Namespace:
instance = super().__new__(cls)
Expand Down
12 changes: 7 additions & 5 deletions jarguments/tests/example.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from jarguments import JParser, JBool, JStr
import jarguments as j

args = JParser(
JBool('show_text', help='determines whether "text" is shown'),
JStr('text'),
args = j.JParser(
j.JBool('show-text', help='determines whether "text" is shown'),
j.JInt('number', default=1),
j.JArgument('text', type=str, multiple=True),
)

if args.show_text:
print(args.text)
for _ in range(args.number):
print(args.text)

0 comments on commit 0dc7a13

Please sign in to comment.