Beispiel #1
0
 def _show_collect_results(self):
     """Display results of collecting source strings from files."""
     total_strings = sum([x[1] for x in self.stats['strings']])
     Color.echo('Processed [warn]{}[end] files and found [warn]{}[end] '
                'translatable strings in [warn]{}[end] of them.'.format(
                    self.stats['processed_files'], total_strings,
                    len(self.stats)))
Beispiel #2
0
    def push_strings(self):
        """Push strings to the CDS."""
        total = len(self.string_collection.strings)
        if total == 0:
            Color.echo('[warn]There are no strings to push to Transifex.[end]')
            return

        Color.echo('Pushing [warn]{}[end] unique translatable strings '
                   'to Transifex...'.format(total))
        status_code, response_content = tx.push_source_strings(
            self.string_collection.strings.values(), self.purge)
        self._show_push_results(status_code, response_content)
Beispiel #3
0
    def _show_push_results(self, status_code, response_content):
        """Display results of pushing the source strings to CDS.

        :param int status_code: the HTTP status code
        :param dict response_content: the content of the response
        """
        try:
            if 200 <= status_code < 300:
                # {"created":0,"updated":5,"skipped":1,"deleted":0,"failed":0,"errors":[]}
                created = response_content.get('created')
                updated = response_content.get('updated')
                skipped = response_content.get('skipped')
                deleted = response_content.get('deleted')
                failed = response_content.get('failed')
                errors = response_content.get('errors', [])
                Color.echo(
                    '[green]\nSuccessfully pushed strings to Transifex.[end]\n'
                    '[high]Status:[end] [warn]{code}[end]\n'
                    '[high]Created strings:[end] [warn]{created}[end]\n'
                    '[high]Updated strings:[end] [warn]{updated}[end]\n'
                    '[high]Skipped strings:[end] [warn]{skipped}[end]\n'
                    '[high]Deleted strings:[end] [warn]{deleted}[end]\n'
                    '[high]Failed strings:[end] [warn]{failed}[end]\n'
                    '[high]Errors:[end] {errors}[end]\n'.format(
                        code=status_code,
                        created=created,
                        updated=updated,
                        skipped=skipped,
                        deleted=deleted,
                        failed=failed,
                        errors='\n'.join(errors)))

            else:
                message = response_content.get('message')
                details = response_content.get('details')
                Color.echo(
                    '[error]\nCould not push strings to Transifex.[end]\n'
                    '[high]Status:[end] [warn]{code}[end]\n'
                    '[high]Message:[end] [warn]{message}[end]\n'
                    '[high]Details:[end] [warn]{details}[end]\n'.format(
                        code=status_code,
                        message=message,
                        details=json.dumps(details, indent=4),
                    ))
        except Exception:
            self.output('(Error while printing formatted report, '
                        'falling back to raw format)\n'
                        'Status: {code}\n'
                        'Content: {content}'.format(
                            code=status_code,
                            content=response_content,
                        ))
Beispiel #4
0
 def _show_collect_results(self):
     """Display results of collecting source strings from files."""
     total_strings = sum([x[1] for x in self.stats['strings']])
     Color.echo('Processed [warn]{}[end] files and found [warn]{}[end] '
                'translatable strings in [warn]{}[end] of them.'.format(
                    self.stats['processed_files'], total_strings,
                    len(self.stats)))
     if self.verbose_output:
         file_list = '\n'.join([
             u'[pink]{}.[end] {}'.format((cnt + 1), string_repr(x)) for cnt,
             x in enumerate(self.string_collection.strings.values())
         ])
         Color.echo(file_list)
def test(template_str, context_dict=None, autoescape=True, i=''):
    """ Use the django templating engine to run a test.

        Arguments:

        :param template_str: The template to render
        :param context_dict: The context to render the template against
        :param autoescape:   Pretend the django templating engine was setup
                             with autoescape or not (in most real use-cases, it
                             will have been set up with autoescape=True)
        :param i:            Prepend the output with this in order to help
                             distinguish tests when multiple are run

        Information about (auto)escaping in django:
        https://docs.djangoproject.com/en/3.0/ref/templates/language/#automatic-html-escaping  # noqa
    """

    if context_dict is None:
        context_dict = {}
    context = Context(dict(context_dict), autoescape=autoescape)
    template = ('{% load transifex %}' + template_str)
    try:
        result = Template(template).render(context)
    except Exception:
        print(template_str, context_dict, autoescape)
        raise
    Color.echo("[warn]{i:4}[end]. [cyan]Template[end]:    {template}".
               format(i=i, template=template_str))
    Color.echo("      [cyan]Context[end]:     {context}".
               format(context=context_dict))
    Color.echo("      [cyan]Autoescape[end]:  {autoescape}".
               format(autoescape=autoescape))
    Color.echo("      [cyan]Result[end]:      [green]{result}[end]".
               format(result=result))
    print()
Beispiel #6
0
    def push_strings(self):
        """Push strings to the CDS."""
        total = len(self.string_collection.strings)
        if total == 0:
            Color.echo('[warn]There are no strings to push to Transifex.[end]')
            return

        Color.echo('Pushing [warn]{}[end] unique translatable strings '
                   'to Transifex...'.format(total))
        status_code, response_content = tx.push_source_strings(
            self.string_collection.strings.values(), self.purge)

        if self.no_wait:
            Color.echo('Queued')
            return

        job_url = response_content['data']['links']['job']
        status = 'starting'
        while status in ['starting', 'pending', 'processing']:
            time.sleep(1)
            status_code, response_content = tx.get_push_status(job_url)
            new_status = response_content['data']['status']
            if new_status != status:
                status = new_status
                if status == 'pending':
                    sys.stdout.write('In queue...')
                elif status == 'processing':
                    sys.stdout.write('Processing...')
            else:
                sys.stdout.write('.')
            sys.stdout.flush()

        Color.echo('')
        self._show_push_results(status_code, response_content)
Beispiel #7
0
    def mark_file(self, file_migration):
        if not file_migration.low_confidence_strings:
            return False

        first_string_migration = file_migration.strings[0]
        if MARK_PROOFREAD_FILE in first_string_migration.new:
            return False

        mark_string(
            first_string_migration,
            self._comment_format,
            MARK_PROOFREAD_FILE,
        )
        Color.echo('📝 File automatically marked for proofreading')
        return True
def migrate_text(text, migrator_func):
    """Convert the given text from the original framework to Native syntax.

    Supports both HTML/template syntax and Python/gettext syntax.
    Prints out the result in the console.

    :param unicode text: the text to migrate to Native syntax
    :param callable migrator_func: a Callable[unicode] -> FileMigration object
        that converts syntax to Transifex Native; provided externally
        so that it can support any Python framework (e.g. Django)
    """
    Color.echo('[high]Original syntax:[end]\n[red]{}[end]'.format(text))
    file_migration = migrator_func(text)
    Color.echo('\n[high]Transifex Native syntax:[end]\n[green]{}[end]'.format(
        file_migration.compile()))
Beispiel #9
0
def test_text_migration_python_code(mock_echo):
    """Test the mode that migrates directly given text instead of files
    (Python/gettext code)."""
    command = Command()
    call_command(command, 'migrate', text=PYTHON_SAMPLE)
    expected = Color.format(
        '\n[high]Transifex Native syntax:[end]\n[green]{}[end]'.format(
            PYTHON_SAMPLE_MIGRATED))
    native = Color.format(mock_echo.call_args_list[1][0][0])
    assert expected == native

    # Make sure it's idempotent
    mock_echo.reset_mock()
    call_command(command, 'migrate', text=PYTHON_SAMPLE_MIGRATED)
    actual = Color.format(mock_echo.call_args_list[1][0][0])
    assert expected == actual
Beispiel #10
0
def test_text_migration_template_code(mock_echo):
    """Test the mode that migrates directly given text instead of files
    (Django HTML templates)."""
    command = Command()
    call_command(command, 'migrate', text=DJANGO_TEMPLATE)
    expected = Color.format(
        '\n[high]Transifex Native syntax:[end]\n[green]{}[end]'.format(
            TRANSIFEX_TEMPLATE))
    actual = Color.format(mock_echo.call_args_list[1][0][0])
    assert expected == actual

    # Make sure it's idempotent
    mock_echo.reset_mock()
    call_command(command, 'migrate', text=TRANSIFEX_TEMPLATE)
    actual = Color.format(mock_echo.call_args_list[1][0][0])
    assert expected == actual
Beispiel #11
0
    def collect_strings(self):
        """Search all related files, collect and store translatable strings.

        Stores found strings in `self.string_collection`.
        """
        Color.echo(
            '[high]\n'
            '##############################################################\n'
            'Transifex Native: Parsing files to detect translatable content'
            '[end]')
        files = self._find_files('.', 'push')
        for f in files:
            extracted_strings = self._extract_strings(f)
            self.string_collection.extend(extracted_strings)
            self.stats['processed_files'] += 1
            if extracted_strings and len(extracted_strings):
                self.stats['strings'].append((f.file, len(extracted_strings)))

        # Append optional CLI tags
        if self.append_tags:
            extra_tags = [x.strip() for x in self.append_tags.split(',')]
            for key, string in self.string_collection.strings.items():
                new_string_tags = set(string.tags + extra_tags)
                string.meta[consts.KEY_TAGS] = list(new_string_tags)

        # Filter out strings based on tags, i.e. only push strings
        # that contain certain tags or do not contain certain tags
        if self.with_tags_only:
            included_tags = {x.strip() for x in self.with_tags_only.split(',')}
        else:
            included_tags = set()
        if self.without_tags_only:
            excluded_tags = {
                x.strip()
                for x in self.without_tags_only.split(',')
            }
        else:
            excluded_tags = set()

        if included_tags or excluded_tags:
            self.string_collection.update([
                string
                for key, string in self.string_collection.strings.items()
                if included_tags.issubset(set(string.tags))
                and not excluded_tags.intersection(set(string.tags))
            ])
        self._show_collect_results()
Beispiel #12
0
    def handle(self, *args, **options):
        purge = options['purge']

        if purge:
            Color.echo('Purging CDS cache...')
        else:
            Color.echo('Invalidating CDS cache...')

        status_code, response_content = tx.invalidate_cache(purge)

        try:
            if 200 <= status_code < 300:
                # {"data": { "count":0 }}
                count = response_content['data']['count']
                if purge:
                    Color.echo('[green]\nSuccessfully purged CDS cache.[end]\n'
                               '[high]Status:[end] [warn]{code}[end]\n'
                               '[high]Records purged: {count}[end]\n'.format(
                                   code=status_code,
                                   count=count,
                               ))
                else:
                    Color.echo(
                        '[green]\nSuccessfully invalidated CDS cache.[end]\n'
                        '[high]Status:[end] [warn]{code}[end]\n'
                        '[high]Records invalidated: {count}[end]\n'
                        '[high]Note: It might take a few minutes for '
                        'fresh content to be available\n'.format(
                            code=status_code,
                            count=count,
                        ))
            else:
                message = response_content.get('message')
                Color.echo('[error]\nCould not invalidate CDS.[end]\n'
                           '[high]Status:[end] [warn]{code}[end]\n'
                           '[high]Message:[end] [warn]{message}[end]\n'.format(
                               code=status_code,
                               message=message,
                           ))
        except Exception:
            self.output('(Error while printing formatted report, '
                        'falling back to raw format)\n'
                        'Status: {code}\n'
                        'Content: {content}'.format(
                            code=status_code,
                            content=response_content,
                        ))
Beispiel #13
0
    def collect_strings(self):
        """Search all related files, collect and store translatable strings.

        Stores found strings in `self.string_collection`.
        """
        Color.echo(
            '[high]\n'
            '##############################################################\n'
            'Transifex Native: Parsing files to detect translatable content'
            '[end]')
        files = self._find_files('.', 'push')
        for f in files:
            extracted = self._extract_strings(f)
            self.string_collection.extend(extracted)
            self.stats['processed_files'] += 1
            if extracted and len(extracted):
                self.stats['strings'].append((f.file, len(extracted)))
        self._show_collect_results()
def test_invalidate_cache_fail(mock_echo, mock_invalidate_cache):
    mock_invalidate_cache.return_value = 500, {
        'message': 'error message',
        'details': 'error details',
    }

    expected = Color.format(
        '[error]\nCould not invalidate CDS.[end]\n'
        '[high]Status:[end] [warn]{code}[end]\n'
        '[high]Message:[end] [warn]{message}[end]\n'.format(
            code=500,
            message='error message',
        ))

    # Make sure it's idempotent
    mock_echo.reset_mock()
    command = get_transifex_command()
    call_command(command, 'invalidate')
    actual = Color.format(mock_echo.call_args_list[1][0][0])
    assert expected == actual
def test_purge_cache_success(mock_echo, mock_invalidate_cache):
    mock_invalidate_cache.return_value = 200, {
        'data': {
            'count': 5,
        }
    }

    expected = Color.format('[green]\nSuccessfully purged CDS cache.[end]\n'
                            '[high]Status:[end] [warn]{code}[end]\n'
                            '[high]Records purged: {count}[end]\n'.format(
                                code=200,
                                count=5,
                            ))

    # Make sure it's idempotent
    mock_echo.reset_mock()
    command = get_transifex_command()
    call_command(command, 'invalidate', purge=True)
    actual = Color.format(mock_echo.call_args_list[1][0][0])
    assert expected == actual
Beispiel #16
0
 def print_original_file(file_migration):
     """Print all the lines of the file as it was originally,
     without any highlighting."""
     Color.echo('[prompt]This is the original file[end]')
     Color.echo('[prompt]-------------------------------------[end]')
     print(add_line_prefix(''.join(file_migration.original_content), '', 0))
     Color.echo('[prompt]-------------------------------------[end]')
Beispiel #17
0
    def _safe_save(self, path, content_func, file_type):
        """Attempt to save the string provided by the given callable to the
        given file path, gracefully handling any exception.

        Requires a callable so that it also catches any exceptions raised
        during the generation of the content.

        Usage:
        >>> _safe_save('file/path.html', lambda: content, file_type='Backup')  # noqa
        >>> _safe_save('file/path.html', my_provider.get_content, file_type='Backup')  # noqa

        :param basestring path: the path to save to
        :param callable content_func: a callable that should return the content
            to save in the file
        :param basestring file_type: the type of the file that was saved,
            used to display a more clear message to the user
            e.g. 'backup' or 'original'
        :return: a tuple that shows if the file was saved and the type of
            exception raised if applicable
        :rtype: Tuple[bool, type]
        """
        try:
            with io.open(path, "w", encoding="utf-8") as f:
                f.write(content_func())
                Color.echo('💾️ {} file saved at [file]{}[end]'.format(
                    file_type, path))
                return True, None
        except IOError as e:
            Color.echo('❌ [red]IOError while saving to {} file[end] '
                       '[file]{}[end]: {}'.format(file_type.lower(), path, e))
            return False, type(e)
        except Exception as e:
            Color.echo('❌ [red]Error while saving to {} file[end]'
                       ' [file]{}[end]: {}'.format(file_type.lower(), path, e))
            return False, type(e)
Beispiel #18
0
    def print_new_file(file_migration):
        """Print all the lines of the file, highlighting the new chars only."""
        Color.echo('[prompt]This is the final file[end]')
        Color.echo('[prompt]-------------------------------------[end]')

        output = []
        for string_migration in file_migration.strings:
            if not string_migration.modified:
                output.append(Color.format(string_migration.original))
            else:
                output.append(
                    Color.format('[green]{}[end]'.format(
                        string_migration.new)))

        print(add_line_prefix(''.join(output), '', 0))
        Color.echo('[prompt]-------------------------------------[end]')
Beispiel #19
0
            def add_in_between(txt_range):
                """Add the simple text found between the last migrated node
                and the current node.

                Ensures that the text that should remain untransformed
                is included in the final file.
                """
                if txt_range[0] > last_migrated_char:
                    try:
                        original_str = src[last_migrated_char:txt_range[0]]
                        file_migration.add_string(
                            StringMigration(
                                original=original_str,
                                new=original_str,
                                confidence=Confidence.HIGH,
                            ))
                    except Exception as e:
                        Color.echo(
                            '[error]Error while adding in-between content '
                            'in [file]{}[end]. last_migrated_char={}.'
                            'Error: {}'.format(filename, last_migrated_char,
                                               e))
                        raise
Beispiel #20
0
    def save_file(self, file_migration):
        """Save the new content in the original path, but take a backup first.

        :param FileMigration file_migration: the migration of a whole file
        """
        # Save the original content in a backup file
        backup_filename = file_migration.filename + '.bak'
        success = self._safe_save(
            backup_filename,
            lambda: file_migration.original_content,
            file_type='Backup',
        )

        # If the backup failed, do not modify the original file
        if not success:
            Color.echo('[warn]  -> will not modify the original file '
                       '[file]{}[end]'.format(file_migration.filename))
            return False

        # Save the new content in the original file
        return self._safe_save(file_migration.filename,
                               file_migration.compile,
                               file_type='Original')
def test_invalidate_cache_success(mock_echo, mock_invalidate_cache):
    mock_invalidate_cache.return_value = 200, {
        'data': {
            'count': 5,
        },
    }

    expected = Color.format(
        '[green]\nSuccessfully invalidated CDS cache.[end]\n'
        '[high]Status:[end] [warn]{code}[end]\n'
        '[high]Records invalidated: {count}[end]\n'
        '[high]Note: It might take a few minutes for '
        'fresh content to be available\n'.format(
            code=200,
            count=5,
        ))

    # Make sure it's idempotent
    mock_echo.reset_mock()
    command = get_transifex_command()
    call_command(command, 'invalidate')
    actual = Color.format(mock_echo.call_args_list[1][0][0])
    assert expected == actual
Beispiel #22
0
def yes_no(description, yes_message=None, no_message=None):
    """Prompts the user to reply to a Yes/No question.

    :param basestring description: the message to display before prompting
    :param basestring yes_message: the message to display if user accepts
    :param basestring no_message: the message to display is user declines
    :return: True if the user chose to go through, false otherwise
    :rtype: bool
    """
    while True:
        reply = prompt(
            Color.format('[opt](Y)[end] Yes [opt](N)[end] No'),
            description=description,
            default='N',
        )
        reply = reply.upper()
        if reply == 'Y':
            if yes_message:
                Color.echo('[high]{}[end]'.format(yes_message))
            return True
        elif reply == 'N':
            if no_message:
                Color.echo('[high]{}[end]'.format(no_message))
            return False
Beispiel #23
0
 def _file_prompt_intro(self):
     """Prompt the user with available actions when reviewing a file."""
     reply = prompt(
         Color.format('[opt](A)[end] Accept '
                      '[opt](R)[end] Reject '
                      '[opt](M)[end] Accept & Mark for proofreading '
                      '\n'
                      '[opt](P)[end] Print diff only '
                      '[opt](PP)[end] Print new file with diff '
                      '[opt](O)[end] Print original file '
                      '[opt](F)[end] Print new file '
                      '\n'
                      '[opt](AA)[end] Accept remaining files '
                      '[opt](X)[end] Exit the migration'),
         default=str(ACCEPT_CHOICE),
     )
     return reply.upper()
Beispiel #24
0
    def _prompt_to_start(self, total_files):
        """Prompt the user before starting the migration.

        If the user chooses to not go through with it, sys.exit() is called.

        :param int total_files: the total number of files to migrate
        """
        msg = pluralized(
            'Found [warn]{cnt}[end] file to check for translatable strings.',
            'Found [warn]{cnt}[end] files to check for translatable strings.',
            total_files,
        )
        Color.echo('\n{}'.format(msg))

        if not total_files:
            Color.echo('\n[high]Migration ended.[end]')
            sys.exit(1)

        if (self.options['save_policy'] != NoopSavePolicy.name
                and self.options['review_policy'] == NoopReviewPolicy.name):
            Color.echo(
                '\n[warn]WARNING! The selected configuration will save '
                'all files automatically, without allowing you to do any '
                'reviewing first.[end]')

        while True:
            reply = prompt(
                Color.format('[opt](Y)[end] Yes [opt](N)[end] No'),
                description='Are you sure you want to continue?',
                default='N',
            )
            reply = reply.upper()
            if reply == 'Y':
                return
            elif reply == 'N':
                Color.echo('\n[high]Migration aborted.[end]')
                sys.exit(1)
def fancy_input(text, *choices):
    """ Multiple choice input

        Given arguments ('Which tag do you want to use?',
                         ('t', 't', '{% t ... %}'),
                         ('ut', 'ut', '{% ut ... %}'))

        It will render:

            ===> Which tag do you want to use?
            .... 1. t                 : {% t ... %}
            .... 2. ut                : {% ut ... %}
            .... [examples: "1", "1 3"; empty input for all choices]

        The return value will be a list of "choices", ie the first items in the
        3-tuples.
    """

    print()
    Color.echo("[yel]===>[end] {}".format(text))
    for i, (_, display, example) in enumerate(choices, 1):
        line = "[yel]....[end] [warn]{}[end]. {:18}".format(i, display)
        if example:
            line += ": [cyan]{}[end]".format(example)
        Color.echo(line)

    while True:
        Color.echo('.... [examples: "[warn]1[end]", "[warn]1 3[end]"; '
                   '[warn]empty input[end] for all choices]')
        answer = input("===> ")
        if answer.strip() == "":
            return [choice for choice, _, _ in choices]
        try:
            answer = [int(choice) - 1 for choice in answer.split()]
        except Exception:
            pass
        else:
            if all((0 <= choice < len(choices) for choice in answer)):
                return [choices[choice][0] for choice in answer]
        print("Invalid answer, please try again")
Beispiel #26
0
    def _show_results(self, files, stats):
        """Show a detailed report of how the migration went.

        :param list files: a list of TranslatableFile objects
        :param dict stats: a dictionary with all statistics of the execution
        """
        Color.echo('\n\n[high]Migration completed![end]')
        Color.echo('--------------------')
        Color.echo('[high]Files found:[end] [warn]{}[end]'.format(len(files)))
        Color.echo('[high]Files processed:[end] [warn]{}[end]'.format(
            stats['processed_files']))

        files_modified = 0
        strings_modified = 0
        for _, file_migration in stats['migrations']:
            new_string_count = len(file_migration.modified_strings)
            strings_modified += new_string_count
            if new_string_count:
                files_modified += 1

        # Files & string migrations
        Color.echo('[high]File migrations created:[end] [warn]{}[end]'.format(
            files_modified))
        Color.echo(
            '[high]String migrations inside these files: [warn]{}[end]'.format(
                strings_modified))

        # Files saved
        Color.echo('[high]Files saved:[end] [warn]{}[end]'.format(
            len(stats['saved'])))
        saved_str = '\n'.join(
            [' - [file]{}[end]'.format(x.filename) for x in stats['saved']])
        if saved_str:
            Color.echo(saved_str)

        # Files & strings marked
        Color.echo(
            '[high]Files marked for proofreading:[end] [warn]{}[end]'.format(
                stats['files_marked']))
        Color.echo(
            '[high]Strings marked for proofreading:[end] [warn]{}[end]'.format(
                stats['strings_marked']))

        # Errors found
        Color.echo('[high]Errors found:[end] [warn]{}[end]'.format(
            len(stats['errors'])))
        errors_str = '\n'.join(
            [' - [warn]{}[end]'.format(x.filename) for x in stats['errors']])
        if errors_str:
            Color.echo(errors_str)

        Color.echo('')
Beispiel #27
0
 def show_intro(self):
     """Show an introductory message to help the user understand what
     is going on.
     """
     Color.echo(
         '[high]'
         '\n############################################################'
         '########\n'
         'Running migration from Django i18n syntax to Transifex Native '
         'syntax\n'
         '[end]'
         '\nThis migration is idempotent, so its output should not '
         'change if run'
         '\nmultiple times with the same configuration.')
     Color.echo('\n[high]Configuration:[end]')
     if self.options['path']:
         Color.echo('[opt]Path:[end] [file]{}[end]'.format(
             self.options['path']))
     if self.options['files']:
         Color.echo('[opt]Files:[end]')
         Color.echo('\n'.join(
             [' - [file]{}[end]'.format(x) for x in self.options['files']]))
     Color.echo('[opt]Review policy:[end] [high]{}[end] -> {}'.format(
         self.options['review_policy'],
         REVIEW_POLICY_OPTIONS[self.options['review_policy']],
     ).strip())
     Color.echo('[opt]Save policy:[end] [high]{}[end] -> {}'.format(
         self.options['save_policy'],
         SAVE_POLICY_OPTIONS[self.options['save_policy']],
     ).strip())
     Color.echo('[opt]Mark policy:[end] [high]{}[end] -> {}'.format(
         self.options['mark_policy'],
         MARK_POLICY_OPTIONS[self.options['mark_policy']],
     ).strip())
Beispiel #28
0
    def migrate_files(self, files):
        """Search all related files, detect Django i18n translate hooks and
        migrate them to Transifex syntax.

        :param list files: a list of TranslatableFile objects
        """
        files_total = len(files)

        # Ask the user for permission to continue
        self._prompt_to_start(len(files))

        accept_remaining_files = False
        exit_migration = False

        # Loop through each file, migrate, ask for user review if applicable,
        # save to disk if applicable
        for file_cnt, translatable_file in enumerate(files):
            if exit_migration:
                break

            comment_format = self._comment_format(translatable_file.file)
            self.review_policy.set_comment_format(comment_format)
            self.mark_policy.set_comment_format(comment_format)

            Color.echo('\n---- '
                       '[[high]{cnt}[end]/[high]{total}[end]] '
                       'Migrating [file]{path}[end]...'.format(
                           path=translatable_file.path,
                           cnt=file_cnt + 1,
                           total=files_total,
                       ))
            self.stats['processed_files'] += 1
            file_migration = self.file_migrator_func(translatable_file)
            if not file_migration:
                continue

            modified_strings = file_migration.modified_strings
            total_modified = len(modified_strings)
            total_low_confidence = len([
                x for x in modified_strings if x.confidence == Confidence.LOW
            ])
            msg = pluralized(
                '[warn]1[end] [prompt]string was modified[end]',
                '[warn]{cnt}[end] [prompt]strings were modified[end]',
                total_modified,
            )
            Color.echo('{msg}{confidence}'.format(
                msg=msg,
                confidence=(' ([warn]{low}[end] with low confidence)'.format(
                    low=total_low_confidence, )
                            if total_low_confidence else '')))
            if not total_modified:
                continue

            # If the review policy says so, prompt the user for each string
            # If this returns True, it doesn't necessarily mean that the user
            # will be prompted, as the actual review policy may use
            # additional filters, e.g. only prompt for strings with low
            # confidence
            if self.review_policy.should_review_strings():
                reject_remaining_strings = False
                for string_index in range(total_modified):
                    string_migration = modified_strings[string_index]

                    # The rest of the string test_migrations should be reverted
                    # based on the user's choice
                    if reject_remaining_strings:
                        string_migration.revert()

                    # Optionally prompt the user to review the migration
                    else:
                        # Give the user the option to review
                        # May modify `string_migration` in place
                        result = self.review_policy.review_string(
                            string_migration, string_index, total_modified)
                        # The user has chosen to accept the changes
                        # in all remaining strings. Break so that
                        # there will be no more prompts for the rest
                        # of the strings
                        if result == REVIEW_ACCEPT_ALL:
                            break

                        # The user has chosen to reject the changes in all
                        # remaining strings. Set the flag to True, so that
                        # it will revert all changes for the rest strings
                        # in the loop
                        elif result == REVIEW_REJECT_ALL:
                            reject_remaining_strings = True

                        # The user has chosen to exit the migration completely
                        # Break to exit the outer (file) loop
                        elif result == REVIEW_EXIT:
                            exit_migration = True
                            break

            # If the mark policy says so, give it a chance to mark
            # each string for proofread
            if self.mark_policy.should_mark_strings():
                for string_migration in modified_strings:
                    marked = self.mark_policy.mark_string(string_migration)
                    if marked:
                        self.stats['strings_marked'] += 1

            # If the review policy says so, prompt the user for each file
            if accept_remaining_files is False and exit_migration is False:
                result = self.review_policy.review_file(file_migration)

                # The user has chosen to reject all remaining files
                # Break, so that we exit the outer (file) loop
                if result == REVIEW_REJECT_ALL:
                    break

                # The user has chosen to accept all remaining files
                # Set the flag, so that the file review policy won't be used
                # for the remaining file test_migrations
                elif result == REVIEW_ACCEPT_ALL:
                    accept_remaining_files = True

                # The user has chosen to exit the migration completely
                elif result == REVIEW_EXIT:
                    exit_migration = True

            # Skip to the results
            # Break to exit the outer (file) loop
            if exit_migration is True:
                break

            # Give a chance to the mark policy to mark the file for proofread
            marked = self.mark_policy.mark_file(file_migration)
            if marked:
                self.stats['files_marked'] += 1

            # If the save policy says so, save the changes
            if file_migration.modified_strings:
                saved, error_type = self.save_policy.save_file(file_migration)
            else:
                saved, error_type = False, None

            # Update stats
            self.stats['migrations'].append(
                (translatable_file.path, file_migration))
            if saved:
                self.stats['saved'].append(file_migration)
            elif error_type is not None:
                self.stats['errors'].append(file_migration)

        self._show_results(files, self.stats)
Beispiel #29
0
 def output(self, msg):
     Color.echo(msg)
Beispiel #30
0
 def verbose(self, msg):
     if self.verbose_output:
         Color.echo(msg)