Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions orm
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This can be used by running "python craft". This module is not ran when the CLI
successfully import commands for you.
"""

from cleo import Application
from cleo.application import Application
from src.masoniteorm.commands import (
MigrateCommand,
MigrateRollbackCommand,
Expand All @@ -21,7 +21,7 @@ from src.masoniteorm.commands import (
SeedRunCommand,
)

application = Application("ORM Version:", 0.1)
application = Application("ORM Version:", "0.1")

application.add(MigrateCommand())
application.add(MigrateRollbackCommand())
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ psycopg2-binary
python-dotenv>=0.14
pyodbc
pendulum>=3.0,<4.0
cleo>=0.8.0,<2.0
cleo>=2.1,<3
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version="3.0.2",
version="3.1.0",
package_dir={"": "src"},
description="The Official Masonite ORM",
long_description=long_description,
Expand All @@ -33,7 +33,7 @@
install_requires=[
"inflection>=0.3,<0.6",
"pendulum>=3.0,<4.0",
"cleo>=0.8.0,<2.0",
"cleo>=2.1,<3",
],
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[
Expand Down
16 changes: 0 additions & 16 deletions src/masoniteorm/commands/CanOverrideConfig.py

This file was deleted.

22 changes: 0 additions & 22 deletions src/masoniteorm/commands/CanOverrideOptionsDefault.py

This file was deleted.

175 changes: 171 additions & 4 deletions src/masoniteorm/commands/Command.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,173 @@
from .CanOverrideConfig import CanOverrideConfig
from .CanOverrideOptionsDefault import CanOverrideOptionsDefault
import re

from cleo.commands.command import Command as BaseCommand
from cleo.helpers import argument, option
from inflection import underscore

class Command(CanOverrideOptionsDefault, CanOverrideConfig):
pass
_TOKEN_RE = re.compile(r"\{([^}]+)\}")
_NAME_RE = re.compile(r"[A-Za-z0-9:_\-]+")


class Command(BaseCommand):
"""Masonite ORM's base command.

Commands declare their signature in the class docstring using the
historical cleo 0.8 block format, which this class translates into a
cleo 2 definition:

Description of the command

command:name
{argument : Argument description}
{optional_argument? : Optional argument}
{--flag : Boolean option}
{--s|--long : Option with a shortcut}
{--option=? : Option expecting a value}
{--option=default : Option with a default value}

Optional option defaults can be overridden when instantiating the
command: SomeCommand(directory="other/default"). If an option long name
uses - then use _ in the keyword argument.

Every command also gets a global --config/-C option pointing to the ORM
configuration file.
"""

def __init__(self, **kwargs):
self._configure_from_docstring()
super().__init__()

# global --config option available on every ORM command
self._definition.add_option(
option(
"config",
"C",
"The path to the ORM configuration file. If not given DB_CONFIG_PATH "
"env variable will be used and finally 'config.database'.",
flag=False,
value_required=False,
)
)

# allow overriding option defaults per instance
self.overriden_default = kwargs
for definition_option in self._definition.options:
default = self.overriden_default.get(underscore(definition_option.name))
if default:
definition_option.set_default(default)

def option(self, key=None):
value = super().option(key)
if value is None and key is not None:
# cleo 0.8 compatibility: an optional-value option passed without
# a value (e.g. a bare --show) reads as present, hence truthy.
try:
given_options = self.io.input._options
except AttributeError:
return value
if key in given_options:
return True
return value

@classmethod
def _configure_from_docstring(cls):
# configure each concrete command class once
if "_docstring_parsed" in cls.__dict__:
return
doc = cls.__doc__ or ""

name, description = cls._parse_name_and_description(doc)
arguments, options = cls._parse_tokens(doc)

if name:
cls.name = name
if description:
cls.description = description
cls.arguments = arguments
cls.options = options
cls._docstring_parsed = True

@staticmethod
def _parse_name_and_description(doc):
"""The command name is the last text line that looks like a command
slug; every text line before it forms the description."""
text_lines = [
line.strip()
for line in _TOKEN_RE.sub("", doc).splitlines()
if line.strip()
]

name = None
name_index = None
for index, line in enumerate(text_lines):
if _NAME_RE.fullmatch(line):
name = line
name_index = index

description_lines = (
text_lines[:name_index] if name_index is not None else text_lines
)
return name, " ".join(description_lines)

@classmethod
def _parse_tokens(cls, doc):
arguments = []
options = []
for token in _TOKEN_RE.findall(doc):
spec, _, token_description = token.strip().partition(":")
spec = spec.strip()
token_description = token_description.strip() or None

if spec.startswith("--"):
options.append(cls._parse_option(spec, token_description))
else:
arguments.append(cls._parse_argument(spec, token_description))
return arguments, options

@staticmethod
def _parse_option(spec, description):
parts = [part.strip().lstrip("-") for part in spec.split("|")]
short_name = parts[0] if len(parts) > 1 else None
long_name = parts[-1]

flag = True
multiple = False
default = None
if "=" in long_name:
long_name, _, default = long_name.partition("=")
flag = False
if default == "*":
multiple = True
default = None
elif default in ("?", ""):
default = None

return option(
long_name,
short_name,
description,
flag=flag,
value_required=False,
multiple=multiple,
default=default,
)

@staticmethod
def _parse_argument(spec, description):
optional = False
multiple = False
default = None
if spec.endswith("?"):
spec = spec[:-1]
optional = True
elif spec.endswith("*"):
spec = spec[:-1]
optional = True
multiple = True
elif "=" in spec:
spec, _, default = spec.partition("=")
optional = True

return argument(
spec, description, optional=optional, multiple=multiple, default=default
)
4 changes: 2 additions & 2 deletions src/masoniteorm/commands/Entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
successfully import commands for you.
"""

from cleo import Application
from cleo.application import Application

from . import (
MakeMigrationCommand,
Expand All @@ -23,7 +23,7 @@
ShellCommand,
)

application = Application("ORM Version:", 0.1)
application = Application("ORM Version:", "0.1")

application.add(MigrateCommand())
application.add(MigrateRollbackCommand())
Expand Down
2 changes: 1 addition & 1 deletion tests/commands/test_shell.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import unittest
from cleo import CommandTester
from cleo.testers.command_tester import CommandTester

from src.masoniteorm.commands import ShellCommand

Expand Down
Loading