Skip to content
Closed
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: 4 additions & 0 deletions odoo/addons/base/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ def test_docstring(self):
self.assertFalse('\n' in cmd.__doc__ or len(cmd.__doc__) > 120,
msg=f"Command {name}'s docstring format is invalid for 'odoo-bin help'")

def test_unknown_command(self):
command_output = self.run_command('bonbon', check=False).stderr.strip()
self.assertEqual(command_output, "Unknown command 'bonbon'")

def test_help(self):
expected = {
'cloc',
Expand Down
1 change: 0 additions & 1 deletion odoo/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
# Import just the command, the rest will get imported as needed
from .command import Command, main # noqa: F401
from .help import Help # noqa: F401
42 changes: 19 additions & 23 deletions odoo/cli/cloc.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import argparse
import sys
from pathlib import Path

from odoo.tools import cloc, config
from . import Command


class Cloc(Command):
""" Count lines of code per modules """
def run(self, args):
parser = argparse.ArgumentParser(
prog=f'{Path(sys.argv[0]).name} {self.name}',
description="""\
Odoo cloc is a tool to count the number of relevant lines written in
Python, Javascript or XML. This can be used as rough metric for pricing
maintenance of customizations.

It has two modes of operation, either by providing a path:
description = """
Odoo cloc is a tool to count the number of relevant lines written
in Python, Javascript or XML. This can be used as rough metric for
pricing maintenance of customizations.

It has two modes of operation, either by providing a path:

odoo-bin cloc -p module_path
odoo-bin cloc -p module_path

Or by providing the name of a database:
Or by providing the name of a database:

odoo-bin cloc --addons-path=dirs -d database
odoo-bin --addons-path=dirs cloc -d database

In the latter mode, only the custom code is accounted for.
""",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument('--database', '-d', dest="database", help="Database name")
parser.add_argument('--path', '-p', action='append', help="File or directory path")
parser.add_argument('--verbose', '-v', action='count', default=0)
opt, unknown = parser.parse_known_args(args)
In the latter mode, only the custom code is accounted for.
"""

def run(self, args):
self.parser.add_argument('--database', '-d', dest="database", help="Database name")
self.parser.add_argument('--path', '-p', action='append', help="File or directory path")
self.parser.add_argument('--verbose', '-v', action='count', default=0)
opt, unknown = self.parser.parse_known_args(args)
if not opt.database and not opt.path:
parser.print_help()
self.parser.print_help()
sys.exit()
if ',' in opt.database:
sys.exit("-d/--database has multiple database, please provide a single one")
Expand Down
28 changes: 24 additions & 4 deletions odoo/cli/command.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import argparse
import contextlib
import logging
import sys
from inspect import cleandoc
from pathlib import Path

import odoo.cli
import odoo
from odoo.modules import get_module_path, get_modules, initialize_sys_path

commands = {}
"""All loaded commands"""

PROG_NAME = Path(sys.argv[0]).name


class Command:
name = None
prog_name = Path(sys.argv[0]).name
description = None
epilog = None
_parser = None

def __init_subclass__(cls):
cls.name = cls.name or cls.__name__.lower()
commands[cls.name] = cls

@property
def prog(self):
return f"{PROG_NAME} [--addons-path=PATH,...] {self.name}"

@property
def parser(self):
if not self._parser:
self._parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
prog=self.prog,
description=cleandoc(self.description or self.__doc__ or ""),
epilog=cleandoc(self.epilog or ""),
)
return self._parser


def load_internal_commands():
"""Load `commands` from `odoo.cli`"""
Expand Down Expand Up @@ -85,4 +105,4 @@ def main():
o = command()
o.run(args)
else:
sys.exit('Unknown command %r' % (command,))
sys.exit(f"Unknown command {command_name!r}")
17 changes: 7 additions & 10 deletions odoo/cli/db.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import argparse
import io
import urllib.parse
import sys
Expand All @@ -16,20 +14,19 @@

eprint = partial(print, file=sys.stderr, flush=True)


class Db(Command):
""" Create, drop, dump, load databases """
name = 'db'

def run(self, cmdargs):
"""Command-line version of the database manager.
description = """
Command-line version of the database manager.

Doesn't provide a `create` command as that's not useful. Commands are
all filestore-aware.
"""
parser = argparse.ArgumentParser(
prog=f'{Path(sys.argv[0]).name} {self.name}',
description=self.__doc__.strip()
)
"""

def run(self, cmdargs):
parser = self.parser
parser.add_argument('-c', '--config')
parser.add_argument('-D', '--data-dir')
parser.add_argument('--addons-path')
Expand Down
12 changes: 4 additions & 8 deletions odoo/cli/deploy.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import argparse
import os
import requests
import sys
import tempfile
import zipfile
from pathlib import Path

from . import Command


class Deploy(Command):
"""Deploy a module on an Odoo instance"""

def __init__(self):
super(Deploy, self).__init__()
super().__init__()
self.session = requests.session()

def deploy_module(self, module_path, url, login, password, db='', force=False):
Expand Down Expand Up @@ -61,10 +60,7 @@ def zip_module(self, path):
raise

def run(self, cmdargs):
parser = argparse.ArgumentParser(
prog=f'{Path(sys.argv[0]).name} {self.name}',
description=self.__doc__
)
parser = self.parser
parser.add_argument('path', help="Path of the module to deploy")
parser.add_argument('url', nargs='?', help='Url of the server (default=http://localhost:8069)', default="http://localhost:8069")
parser.add_argument('--db', dest='db', help='Database to use if server does not use db-filter.')
Expand Down
14 changes: 3 additions & 11 deletions odoo/cli/genproxytoken.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import argparse
import os
import secrets
import sys
import textwrap
from pathlib import Path

from passlib.hash import pbkdf2_sha512

Expand All @@ -21,13 +17,9 @@ def generate_token(self, length=16):
return '-'.join(textwrap.wrap(token, split_size))

def run(self, cmdargs):
parser = argparse.ArgumentParser(
prog=f'{Path(sys.argv[0]).name} {self.name}',
description=self.__doc__.strip()
)
parser.add_argument('-c', '--config', type=str, help="Specify an alternate config file")
parser.add_argument('--token-length', type=int, help="Token Length", default=16)
args, _ = parser.parse_known_args()
self.parser.add_argument('-c', '--config', type=str, help="Specify an alternate config file")
self.parser.add_argument('--token-length', type=int, help="Token Length", default=16)
args, _ = self.parser.parse_known_args()
if args.config:
config.rcfile = args.config
token = self.generate_token(length=args.token_length)
Expand Down
21 changes: 11 additions & 10 deletions odoo/cli/help.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
from .command import Command, commands, load_addons_commands, load_internal_commands
import textwrap

from .command import PROG_NAME, Command, commands, load_addons_commands, load_internal_commands
import odoo.release


class Help(Command):
""" Display the list of available commands """

template = """\
usage: {prog_name} [--addons-path=PATH,...] <command> [...]
template = textwrap.dedent("""\
usage: {prog_name} [--addons-path=PATH,...] <command> [...]

Odoo {version}
Available commands:
Odoo {version}
Available commands:

{command_list}
{command_list}

Use '{prog_name} server --help' for regular server options.
Use '{prog_name} <command> --help' for other individual commands options.
"""
Use '{prog_name} server --help' for regular server options.
Use '{prog_name} <command> --help' for other individual commands options.
""")

def run(self, args):
load_internal_commands()
Expand All @@ -30,7 +31,7 @@ def run(self, args):
command_list = "\n".join(f" {name:<{padding}}{desc}" for name, desc in name_desc)

print(Help.template.format( # noqa: T201
prog_name=self.prog_name,
prog_name=PROG_NAME,
version=odoo.release.version,
command_list=command_list,
))
4 changes: 1 addition & 3 deletions odoo/cli/neutralize.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import optparse
import sys
from pathlib import Path

import odoo.modules.neutralize
import odoo.sql_db
Expand All @@ -18,7 +16,7 @@ class Neutralize(Command):

def run(self, args):
parser = odoo.tools.config.parser
parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
parser.prog = self.prog
group = optparse.OptionGroup(parser, "Neutralize", "Neutralize the database specified by the `-d` argument.")
group.add_option("--stdout", action="store_true", dest="to_stdout",
help="Output the neutralization SQL instead of applying it")
Expand Down
2 changes: 1 addition & 1 deletion odoo/cli/obfuscate.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo
import sys
import optparse
Expand Down Expand Up @@ -134,6 +133,7 @@ def confirm_not_secure(self):

def run(self, cmdargs):
parser = odoo.tools.config.parser
parser.prog = self.prog
group = optparse.OptionGroup(parser, "Populate Configuration")
group.add_option('--pwd', dest="pwd", default=False, help="Cypher password")
group.add_option('--fields', dest="fields", default=False, help="List of table.columns to obfuscate/unobfuscate: table1.column1,table2.column1,table2.column2")
Expand Down
4 changes: 1 addition & 3 deletions odoo/cli/populate.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
import optparse
import sys
import time
from pathlib import Path

from . import Command
import odoo
Expand All @@ -23,7 +21,7 @@ class Populate(Command):

def run(self, cmdargs):
parser = odoo.tools.config.parser
parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
parser.prog = self.prog
group = optparse.OptionGroup(parser, "Populate Configuration")
group.add_option("--factors", dest="factors",
help="Comma separated list of factors for each model, or just a single factor."
Expand Down
22 changes: 9 additions & 13 deletions odoo/cli/scaffold.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import argparse
import os
import re
import sys
from pathlib import Path

import jinja2

from . import Command


class Scaffold(Command):
""" Generates an Odoo module skeleton. """

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.epilog = "Built-in templates available are: %s" % ', '.join(
d for d in os.listdir(builtins())
if d != 'base'
)

def run(self, cmdargs):
# TODO: bash completion file
parser = argparse.ArgumentParser(
prog=f'{Path(sys.argv[0]).name} {self.name}',
description=self.__doc__,
epilog=self.epilog(),
)
parser = self.parser
parser.add_argument(
'-t', '--template', type=template, default=template('default'),
help="Use a custom module template, can be a template name or the"
Expand Down Expand Up @@ -47,11 +48,6 @@ def run(self, cmdargs):
params=params,
)

def epilog(self):
return "Built-in templates available are: %s" % ', '.join(
d for d in os.listdir(builtins())
if d != 'base'
)

builtins = lambda *args: os.path.join(
os.path.abspath(os.path.dirname(__file__)),
Expand Down
5 changes: 3 additions & 2 deletions odoo/cli/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import os
import re
import sys
from pathlib import Path

from psycopg2.errors import InsufficientPrivilege

Expand Down Expand Up @@ -172,8 +171,10 @@ def main(args):
rc = odoo.service.server.start(preload=config['db_name'], stop=stop)
sys.exit(rc)


class Server(Command):
"""Start the odoo server (default command)"""

def run(self, args):
odoo.tools.config.parser.prog = f'{Path(sys.argv[0]).name} {self.name}'
odoo.tools.config.parser.prog = self.prog
main(args)
Loading