Skip to content

Commit

Permalink
Merge pull request #132 from kobotoolbox/refactoring
Browse files Browse the repository at this point in the history
Refactoring to improved readibility
  • Loading branch information
noliveleger authored Nov 13, 2020
2 parents 32b0daa + 782cf5e commit ce9671a
Show file tree
Hide file tree
Showing 18 changed files with 2,090 additions and 1,964 deletions.
34 changes: 34 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# coding: utf-8
import os
import pytest

# Python retro compatibility
try:
FileNotFoundError
except NameError:
FileNotFoundError = OSError


def clean_up():
"""
Removes files created by tests
"""
files = ['.uniqid',
'upsert_db_users']
for file_ in files:
try:
os.remove(os.path.join('/tmp', file_))
except FileNotFoundError:
pass


@pytest.fixture(scope="session", autouse=True)
def setup(request):
# Clean up before tests begin in case of orphan files.
clean_up()
request.addfinalizer(_tear_down)


def _tear_down():
clean_up()
pass
137 changes: 106 additions & 31 deletions helpers/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import subprocess
import sys
import re
import textwrap

PY2 = sys.version_info[0] == 2
if PY2:
Expand All @@ -14,28 +15,103 @@


class CLI(object):
NO_COLOR = '\033[0m'
COLOR_ERROR = '\033[91m'
COLOR_SUCCESS = '\033[92m'
COLOR_INFO = '\033[94m'
COLOR_WARNING = '\033[95m'

NO_COLOR = '\033[0;0m'
COLOR_ERROR = '\033[0;31m' # dark red
COLOR_SUCCESS = '\033[0;32m' # dark green
COLOR_INFO = '\033[1;34m' # blue
COLOR_WARNING = '\033[1;31m' # red
COLOR_QUESTION = '\033[1;33m' # dark yellow
COLOR_DEFAULT = '\033[1;37m' # white

EMPTY_CHARACTER = '-'

DEFAULT_CHOICES = {
'1': True,
'2': False,
}
# We need an inverted dict version of `DEFAULT_CHOICES` to be able to
# retrieve keys from the values
DEFAULT_RESPONSES = dict(zip(DEFAULT_CHOICES.values(),
DEFAULT_CHOICES.keys()))

@classmethod
def colored_input(cls, message, color=NO_COLOR, default=None):
text = cls.get_message_with_default(message, default)
input_ = input(cls.colorize(text, color))

# User wants to delete value previously entered.
if input_ == '-':
default = ''
input_ = ''

return input_ if input_ is not None and input_ != '' else default

@classmethod
def colored_print(cls, message, color=NO_COLOR):
print(cls.colorize(message, color))

@classmethod
def colorize(cls, message, color=NO_COLOR):
return '{}{}{}'.format(color, message, cls.NO_COLOR)

@classmethod
def framed_print(cls, message, color=COLOR_WARNING, columns=70):
border = '═' * (columns - 2)
framed_message = [
'╔{}╗'.format(border),
'║ {} ║'.format(' ' * (columns - 4)),
]

if not isinstance(message, list):
paragraphs = message.split('\n')
else:
paragraphs = ''.join(message).split('\n')

for paragraph in paragraphs:
if paragraph == '':
framed_message.append(
'║ {} ║'.format(' ' * (columns - 4))
)
continue

for line in textwrap.wrap(paragraph, columns - 4):
message_length = len(line)
spacer = ' ' * (columns - 4 - message_length)
framed_message.append(
'║ {}{} ║'.format(line, spacer)
)

framed_message.append('║ {} ║'.format(' ' * (columns - 4)))
framed_message.append('╚{}╝'.format(border))
cls.colored_print('\n'.join(framed_message), color=color)

@classmethod
def get_response(cls, validators=None, default='', to_lower=True,
error_msg="Sorry, I didn't understand that!"):

use_default = False
# If not validators are provided, let's use default validation
# "Yes/No", where "Yes" equals 1, and "No" equals 2
# Example:
# Are you sure?
# 1) Yes
# 2) No
if validators is None:
use_default = True
default = cls.DEFAULT_RESPONSES[default]
validators = cls.DEFAULT_CHOICES.keys()

while True:
try:
response = cls.colored_input('', cls.COLOR_WARNING, default)
response = cls.colored_input('', cls.COLOR_QUESTION, default)

if (response.lower() in map(lambda x: x.lower(), validators) or
validators is None or
(isinstance(validators, string_type) and
validators.startswith('~') and
re.match(validators[1:], response)
)):
)):
break
else:
cls.colored_print(error_msg,
Expand All @@ -44,35 +120,23 @@ def get_response(cls, validators=None, default='', to_lower=True,
cls.colored_print("Sorry, I didn't understand that.",
cls.COLOR_ERROR)

return response.lower() if to_lower else response

@classmethod
def colored_print(cls, message, color=NO_COLOR):
print(cls.colorize(message, color))

@classmethod
def colored_input(cls, message, color=NO_COLOR, default=None):
text = cls.get_message_with_default(message, default)
input_ = input(cls.colorize(text, color))

# User wants to delete value previously entered.
if input_ == '-':
default = ''
input_ = ''

return input_ if input_ is not None and input_ != '' else default
if use_default:
return cls.DEFAULT_CHOICES[response]

@classmethod
def colorize(cls, message, color=NO_COLOR):
return '{}{}{}'.format(color, message, cls.NO_COLOR)
return response.lower() if to_lower else response

@classmethod
def get_message_with_default(cls, message, default):
message = '{} '.format(message) if message else ''
default = '{}[{}]{}: '.format(cls.COLOR_WARNING,
default,
cls.NO_COLOR) \
if default else ''

if default is None:
default = ''
else:
default = '{white}[{off}{default}{white}]{off}: '.format(
white=cls.COLOR_DEFAULT,
off=cls.NO_COLOR,
default=default
)

if message:
message = '{}: '.format(message.strip()) if not default else message
Expand Down Expand Up @@ -106,3 +170,14 @@ def run_command(cls, command, cwd=None, polling=False):
cls.colored_print('An error has occurred', CLI.COLOR_ERROR)
sys.exit(1)
return stdout

@classmethod
def yes_no_question(cls, question, default=True,
labels=['Yes', 'No']):
cls.colored_print(question, color=cls.COLOR_QUESTION)
for index, label in enumerate(labels):
cls.colored_print('\t{index}) {label}'.format(
index=index + 1,
label=label
))
return cls.get_response(default=default)
Loading

0 comments on commit ce9671a

Please sign in to comment.