diff --git a/.travis.yml b/.travis.yml index 17c3319..e0bc0a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,11 @@ language: python python: - "2.7" + - "3.6" install: - pip install -r requirements.txt - pip install document-your-code # command to run tests script: - - pytest \ No newline at end of file + - pytest + - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then pip install black; black --check --diff --skip-string-normalization .; fi diff --git a/README.md b/README.md index ae8d175..7ab878a 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,8 @@ To get started. $ pip install --editable . ``` +We use [black](https://github.com/python/black) to maintain a standard format for our code. Contributions must be black formatted to pass CI. + ## License MIT © diff --git a/dyc/base.py b/dyc/base.py index 82fd199..86199bb 100644 --- a/dyc/base.py +++ b/dyc/base.py @@ -7,7 +7,7 @@ def __init__(self, filename, config, placeholders=False): self.filename = filename self.config = config self.placeholders = placeholders - + details = dict() def initialize(self, change=None): @@ -25,7 +25,16 @@ def initialize(self, change=None): filename = fileinput.filename() lineno = fileinput.lineno() keywords = self.config.get('keywords') - found = len([word.lstrip() for word in line.split(' ') if word.lstrip() in keywords]) > 0 + found = ( + len( + [ + word.lstrip() + for word in line.split(' ') + if word.lstrip() in keywords + ] + ) + > 0 + ) if change and found: found = self._is_line_part_of_patches(lineno, line, patches) @@ -35,7 +44,9 @@ def initialize(self, change=None): if found: length = get_file_lines(filename) - result = self.extract_and_set_information(filename, lineno, line, length) + result = self.extract_and_set_information( + filename, lineno, line, length + ) if self.validate(result): self.details[filename][result.name] = result @@ -82,7 +93,7 @@ def apply(self): pass -class FilesDirector(): +class FilesDirector: WILD_CARD = ['.', '*'] @@ -133,7 +144,7 @@ def set_files_to_read(self, files=[]): self.file_list = result -class FormatsDirector(): +class FormatsDirector: formats = dict() @@ -146,6 +157,7 @@ def prepare_formats(self): class Processor(FilesDirector, FormatsDirector): """Subclass process that runs complete lifecycle for DYC""" + def start(self): """ TODO Method wrapper for starting the process @@ -170,4 +182,8 @@ def extensions(self): """ Property that returns all the allowed extensions """ - return list(filter(None, map(lambda fmt: fmt.get('extension'), self.config.get('formats')))) + return list( + filter( + None, map(lambda fmt: fmt.get('extension'), self.config.get('formats')) + ) + ) diff --git a/dyc/configs/__init__.py b/dyc/configs/__init__.py index eb32bab..ad7acf7 100644 --- a/dyc/configs/__init__.py +++ b/dyc/configs/__init__.py @@ -5,6 +5,7 @@ DEFAULT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'defaults.yaml') CUSTOM = os.path.join(ROOT_PATH, 'dyc.yaml') + class Config(object): default = read_yaml(DEFAULT) @@ -36,8 +37,10 @@ def _override_formats(self): cnf_index = self._get_custom_extension_index(extension) try: for nested_key, nested_obj in value.iteritems(): - try: - self.plain.get('formats')[cnf_index][nested_key].update(**nested_obj) if nested_obj else None + try: + self.plain.get('formats')[cnf_index][nested_key].update( + **nested_obj + ) if nested_obj else None except AttributeError: continue except (IndexError, TypeError): diff --git a/dyc/diff.py b/dyc/diff.py index 5e2580e..6513ec0 100644 --- a/dyc/diff.py +++ b/dyc/diff.py @@ -13,7 +13,7 @@ from .base import Processor -class DiffParser(): +class DiffParser: PREFIX = 'diff --git' @@ -89,7 +89,7 @@ def __pack(self, patch): for index, line in enumerate(patch): _hunk = get_hunk(line) - if (len(patch) -1) == index: + if (len(patch) - 1) == index: final.append(dict(patch='\n'.join(result), hunk=(start, end))) if len(_hunk) and not hit: @@ -109,7 +109,9 @@ def __pack(self, patch): def __clean(self, patch, diff): """Returns a clean dict of a path""" result = {} - result['additions'] = self.__additions(self.__pack(patch.split('\n')), diff.a_path) # [{hunk: (start, end), patch:}] + result['additions'] = self.__additions( + self.__pack(patch.split('\n')), diff.a_path + ) # [{hunk: (start, end), patch:}] result['plain'] = patch result['diff'] = diff result['name'] = ntpath.basename(diff.a_path) diff --git a/dyc/dyc.py b/dyc/dyc.py index 6e7a155..ee9bf73 100644 --- a/dyc/dyc.py +++ b/dyc/dyc.py @@ -45,9 +45,10 @@ def start(config, files, placeholders): dyc.process_methods() - @main.command() -@click.option('--watch', help='Add default placeholder when watching', is_flag=True, default=False) +@click.option( + '--watch', help='Add default placeholder when watching', is_flag=True, default=False +) @config def diff(config, watch): """ diff --git a/dyc/events.py b/dyc/events.py index 44aa841..e719f04 100644 --- a/dyc/events.py +++ b/dyc/events.py @@ -6,6 +6,7 @@ from .diff import Diff from .main import DYC + class WatchEvent(LoggingEventHandler): config = None @@ -20,15 +21,23 @@ def dispatch(self, event): """ diff = Diff(self.config.plain) uncommitted = diff.uncommitted - paths = [idx.get('path') for idx in uncommitted if './{}'.format(idx.get('path')) == event.src_path] - filtered = [idx for idx in uncommitted if './{}'.format(idx.get('path')) == event.src_path] + paths = [ + idx.get('path') + for idx in uncommitted + if './{}'.format(idx.get('path')) == event.src_path + ] + filtered = [ + idx + for idx in uncommitted + if './{}'.format(idx.get('path')) == event.src_path + ] if len(filtered): dyc = DYC(self.config.plain, placeholders=True) dyc.prepare(files=paths) dyc.process_methods(diff_only=True, changes=uncommitted) -class Watcher: +class Watcher: @classmethod def start(cls, config): """ diff --git a/dyc/exceptions.py b/dyc/exceptions.py index bea982a..f675c68 100644 --- a/dyc/exceptions.py +++ b/dyc/exceptions.py @@ -1,11 +1,35 @@ """ All exception classes are defined here """ -class DYCError(Exception): pass -class SetupError(DYCError): pass -class UndefinedPattern(DYCError): pass -class ConfigurationMissing(Exception): pass -class FormattingConfigurationHandler(ConfigurationMissing): pass -class QuitConfirmEditor(DYCError): pass -class DYCConfigurationSetup(DYCError): pass -class OverrideConfigurations(DYCError): pass \ No newline at end of file + + +class DYCError(Exception): + pass + + +class SetupError(DYCError): + pass + + +class UndefinedPattern(DYCError): + pass + + +class ConfigurationMissing(Exception): + pass + + +class FormattingConfigurationHandler(ConfigurationMissing): + pass + + +class QuitConfirmEditor(DYCError): + pass + + +class DYCConfigurationSetup(DYCError): + pass + + +class OverrideConfigurations(DYCError): + pass diff --git a/dyc/hooks.py b/dyc/hooks.py index 2eb2788..8062daa 100644 --- a/dyc/hooks.py +++ b/dyc/hooks.py @@ -1,3 +1,3 @@ """ -""" \ No newline at end of file +""" diff --git a/dyc/methods.py b/dyc/methods.py index a313737..9820199 100644 --- a/dyc/methods.py +++ b/dyc/methods.py @@ -11,7 +11,6 @@ class MethodBuilder(Builder): already_printed_filepaths = [] # list of already printed files - def extract_and_set_information(self, filename, start, line, length): """ This is a main abstract method tin the builder base @@ -26,14 +25,16 @@ def extract_and_set_information(self, filename, start, line, length): """ start_line = linecache.getline(filename, start) initial_line = line - start_leading_space = get_leading_whitespace(start_line) # Where function started + start_leading_space = get_leading_whitespace( + start_line + ) # Where function started method_string = start_line line_within_scope = True lineno = start + 1 line = linecache.getline(filename, lineno) end_of_file = False end = None - while (line_within_scope and not end_of_file): + while line_within_scope and not end_of_file: current_leading_space = get_leading_whitespace(line) if len(current_leading_space) <= len(start_leading_space) and line.strip(): end = lineno - 1 @@ -47,15 +48,17 @@ def extract_and_set_information(self, filename, start, line, length): end = length linecache.clearcache() - return MethodInterface(plain=method_string, - name=self._get_name(initial_line), - start=start, - end=end, - filename=filename, - arguments=self.extract_arguments(initial_line.strip('\n')), - config=self.config, - leading_space=get_leading_whitespace(initial_line), - placeholders=self.placeholders) + return MethodInterface( + plain=method_string, + name=self._get_name(initial_line), + start=start, + end=end, + filename=filename, + arguments=self.extract_arguments(initial_line.strip('\n')), + config=self.config, + leading_space=get_leading_whitespace(initial_line), + placeholders=self.placeholders, + ) def validate(self, result): """ @@ -68,11 +71,25 @@ def validate(self, result): if not result: return False name = result.name - if name not in self.config.get('ignore', []) and not self.is_first_line_documented(result): - if self.filename not in self.already_printed_filepaths: # Print file of method to document - click.echo("\n\nIn file {} :\n".format(click.style(self.filename, fg='red'))) + if name not in self.config.get( + 'ignore', [] + ) and not self.is_first_line_documented(result): + if ( + self.filename not in self.already_printed_filepaths + ): # Print file of method to document + click.echo( + "\n\nIn file {} :\n".format(click.style(self.filename, fg='red')) + ) self.already_printed_filepaths.append(self.filename) - confirmed = True if self.placeholders else click.confirm('Do you want to document method {}?'.format(click.style(name, fg='green'))) + confirmed = ( + True + if self.placeholders + else click.confirm( + 'Do you want to document method {}?'.format( + click.style(name, fg='green') + ) + ) + ) if confirmed: return True @@ -164,7 +181,7 @@ def _get_name(self, line): return name -class MethodFormatter(): +class MethodFormatter: formatted_string = '{open}{break_after_open}{method_docstring}{break_after_docstring}{empty_line}{argument_format}{break_before_close}{close}' fmt = BlankFormatter() @@ -191,7 +208,7 @@ def wrap_strings(self, words): subs = [] n = self.config.get('words_per_line') for i in range(0, len(words), n): - subs.append(" ".join(words[i:i+n])) + subs.append(" ".join(words[i : i + n])) return '\n'.join(subs) def pre(self): @@ -201,11 +218,23 @@ def pre(self): into consumable values """ method_format = copy.deepcopy(self.config) - method_format['indent'] = get_indent(method_format['indent']) if method_format['indent'] else ' ' - method_format['indent_content'] = get_indent(method_format['indent']) if get_indent(method_format['indent_content']) else '' - method_format['break_after_open'] = '\n' if method_format['break_after_open'] else '' - method_format['break_after_docstring'] = '\n' if method_format['break_after_docstring'] else '' - method_format['break_before_close'] = '\n' if method_format['break_before_close'] else '' + method_format['indent'] = ( + get_indent(method_format['indent']) if method_format['indent'] else ' ' + ) + method_format['indent_content'] = ( + get_indent(method_format['indent']) + if get_indent(method_format['indent_content']) + else '' + ) + method_format['break_after_open'] = ( + '\n' if method_format['break_after_open'] else '' + ) + method_format['break_after_docstring'] = ( + '\n' if method_format['break_after_docstring'] else '' + ) + method_format['break_before_close'] = ( + '\n' if method_format['break_before_close'] else '' + ) method_format['empty_line'] = '\n' argument_format = copy.deepcopy(self.config.get('arguments')) @@ -238,17 +267,25 @@ def build_arguments(self): title = self.argument_format.get('title') if title: underline = '-' * len(title) - self.argument_format['title'] = '{}\n{}\n'.format(title, underline) if config.get('underline') else '{}\n'.format(title) + self.argument_format['title'] = ( + '{}\n{}\n'.format(title, underline) + if config.get('underline') + else '{}\n'.format(title) + ) result = [] if self.arguments: # if len(self.arguments) > 0 for argument_details in self.arg_docstring: argument_details['prefix'] = self.argument_format.get('prefix') - result.append(self.fmt.format(formatted_args, **argument_details).strip()) + result.append( + self.fmt.format(formatted_args, **argument_details).strip() + ) self.argument_format['body'] = '\n'.join(result) - self.method_format['argument_format'] = self.fmt.format('{title}{body}', **self.argument_format) + self.method_format['argument_format'] = self.fmt.format( + '{title}{body}', **self.argument_format + ) def add_indentation(self): """ @@ -280,7 +317,10 @@ def confirm(self, polished): try: result = '\n'.join(method_split) - message = click.edit('## CONFIRM: MODIFY DOCSTRING BETWEEN START AND END LINES ONLY\n\n' + result) + message = click.edit( + '## CONFIRM: MODIFY DOCSTRING BETWEEN START AND END LINES ONLY\n\n' + + result + ) message = '\n'.join(message.split('\n')[2:]) except: print('Quitting the program in the editor terminates the process. Thanks') @@ -314,7 +354,18 @@ def polish(self): class MethodInterface(MethodFormatter): - def __init__(self, plain, name, start, end, filename, arguments, config, leading_space, placeholders): + def __init__( + self, + plain, + name, + start, + end, + filename, + arguments, + config, + leading_space, + placeholders, + ): self.plain = plain self.name = name self.start = start @@ -344,12 +395,15 @@ def _prompt_docstring(self): self.method_docstring = '' else: echo_name = click.style(self.name, fg='green') - self.method_docstring = click.prompt('\n({}) Method docstring '.format(echo_name)) + self.method_docstring = click.prompt( + '\n({}) Method docstring '.format(echo_name) + ) def _prompt_args(self): """ Wrapper for prompting arguments """ + def _echo_arg_style(argument): """ Just a small wrapper for echoing args @@ -361,16 +415,23 @@ def _echo_arg_style(argument): for arg in self.arguments: doc_placeholder = '' - arg_doc = click.prompt('\n({}) Argument docstring '.format(_echo_arg_style(arg))) if not self.placeholders else doc_placeholder + arg_doc = ( + click.prompt('\n({}) Argument docstring '.format(_echo_arg_style(arg))) + if not self.placeholders + else doc_placeholder + ) show_arg_type = self.config.get('arguments', {}).get('add_type', False) if show_arg_type: arg_placeholder = '' - arg_type = click.prompt('({}) Argument type '.format(_echo_arg_style(arg))) if not self.placeholders else arg_placeholder + arg_type = ( + click.prompt('({}) Argument type '.format(_echo_arg_style(arg))) + if not self.placeholders + else arg_placeholder + ) self.arg_docstring.append(dict(type=arg_type, doc=arg_doc, name=arg)) class ArgumentDetails(object): - def __init__(self, line, config): self.line = line self.config = config @@ -383,7 +444,9 @@ def extract(self): try: ignore = self.config.get('ignore') args = re.search(r'\((.*)\)', self.line).group(1).split(', ') - self.args = filter(lambda x: x not in ignore, filter(None, [arg.strip() for arg in args])) + self.args = filter( + lambda x: x not in ignore, filter(None, [arg.strip() for arg in args]) + ) except: pass diff --git a/dyc/parser.py b/dyc/parser.py index c888e43..b8b6558 100644 --- a/dyc/parser.py +++ b/dyc/parser.py @@ -15,4 +15,9 @@ def __init__(self): try: self.override() except AttributeError: - click.echo(click.style('`dyc.yaml` Missing or Incorrectly formatted. USING default settings', fg='cyan')) + click.echo( + click.style( + '`dyc.yaml` Missing or Incorrectly formatted. USING default settings', + fg='cyan', + ) + ) diff --git a/dyc/utils.py b/dyc/utils.py index e3a9453..beaaddf 100644 --- a/dyc/utils.py +++ b/dyc/utils.py @@ -4,11 +4,8 @@ import os import yaml import string -INDENT_OPTIONS = { - 'tab': '\t', - '2 spaces': ' ', - 'False': '', -} + +INDENT_OPTIONS = {'tab': '\t', '2 spaces': ' ', 'False': ''} def get_leading_whitespace(s): @@ -49,7 +46,7 @@ def read_yaml(path): class BlankFormatter(string.Formatter): def __init__(self, default=''): - self.default=default + self.default = default def get_value(self, key, args, kwds): if isinstance(key, str): @@ -71,6 +68,7 @@ def get_indent(space): return ' ' return value + def get_extension(filename): """ Gets the extension of a file @@ -96,7 +94,9 @@ def all_files_generator(extensions=[]): files = [os.path.join(root, f) for f in files if not f[0] == '.'] dirs[:] = [d for d in dirs if not d[0] == '.'] if extensions: - files = [filename for filename in files if get_extension(filename) in extensions] + files = [ + filename for filename in files if get_extension(filename) in extensions + ] yield files @@ -138,6 +138,7 @@ def get_hunk(patch): str patch: Diff patched text """ import re + pat = r'.*?\@\@(.*)\@\@.*' match = re.findall(pat, patch) return [m.strip() for m in match] @@ -150,8 +151,10 @@ def get_additions_in_first_hunk(hunk): ---------- str hunk: Git diff Hunk """ - if not isinstance(hunk, list): return None, None - if len(hunk) < 1: return None, None + if not isinstance(hunk, list): + return None, None + if len(hunk) < 1: + return None, None adds_patch = hunk[0].split('+')[-1].split(',') start = int(adds_patch[0]) end = int(start) + int(adds_patch[1]) diff --git a/requirements.txt b/requirements.txt index 21778aa..429d7dc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ click==7.0 pyyaml>=4.2b1 gitpython==2.1.11 -pytest==4.4.0 \ No newline at end of file +pytest==4.4.0 diff --git a/setup.py b/setup.py index fb57636..4184df8 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="document-your-code", - version="0.1.6", + version="0.1.7", author="Mohammad Albakri", author_email="mohammad.albakri93@gmail.com", packages=find_packages(), @@ -17,12 +17,8 @@ "pyyaml>=4.2b1", "gitpython==2.1.11", "watchdog==0.9.0", - "pytest==4.4.0" - ], - entry_points = { - "console_scripts": ["dyc=dyc.dyc:main"], - }, - package_data={ - '': ['*.yaml'], - }, + "pytest==4.4.0", + ], + entry_points={"console_scripts": ["dyc=dyc.dyc:main"]}, + package_data={'': ['*.yaml']}, ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 35dcb0a..7b5dc27 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,7 @@ from dyc.utils import get_leading_whitespace, read_yaml, get_indent, get_extension -class TestGetLeadingWhitespace(): + +class TestGetLeadingWhitespace: def test_tabs(self): text = '\t\tHello' expected = '\t\t' @@ -15,7 +16,7 @@ def test_whitespace(self): assert expected == got -class TestReadYaml(): +class TestReadYaml: def test_should_return_none_if_not_found(self): random_path = '/path/to/non/existing/file.yaml' expected = None @@ -23,8 +24,7 @@ def test_should_return_none_if_not_found(self): assert expected == got -class TestGetIndent(): - +class TestGetIndent: def test_tabs(self): assert get_indent('tab') == '\t' @@ -38,19 +38,18 @@ def test_default_4_spaces(self): assert get_indent(None) == ' ' -class TestGetExtension(): - +class TestGetExtension: def test_existing_extension_valid(self): ext = 'file.puk' expected = 'puk' got = get_extension(ext) - assert expected == got + assert expected == got def test_non_existing_extension(self): ext = 'file' expected = '' got = get_extension(ext) - assert expected == got + assert expected == got def test_wrong_extension_type(self): exts = [dict(), False, True, [], 123]