Ejemplo n.º 1
0
def _identify(ctx, src, failfast, verbose):
    """Identify files for import.

    Walk the SRC list of files or directories and report each file
    identified by one of the configured importers.  When verbose
    output is requested, also print the account name associated to the
    document by the importer.

    """
    log = utils.logger(verbose)
    errors = exceptions.ExceptionsTrap(log)

    for filename in _walk(src, log):
        with errors:
            importer = identify.identify(ctx.importers, filename)
            if not importer:
                log('')  # Newline.
                continue

            # Signal processing of this document.
            log(' ...', nl=False)

            # When verbose output is requested, get the associated account.
            account = importer.file_account(
                cache.get_file(filename)) if verbose else None

            log(' OK', fg='green')
            log(f'  {importer.name():}')
            log(f'  {account:}', 1)

        if failfast and errors:
            break

    if errors:
        sys.exit(1)
Ejemplo n.º 2
0
def _extract(ctx, src, output, existing, reverse, failfast, quiet):
    """Extract transactions from documents.

    Walk the SRC list of files or directories and extract the ledger
    entries from each file identified by one of the configured
    importers.  The entries are written to the specified output file
    or to the standard output in Beancount ledger format in sections
    associated to the source document.

    """
    verbosity = -quiet
    log = utils.logger(verbosity, err=True)
    errors = exceptions.ExceptionsTrap(log)

    # Load the ledger, if one is specified.
    existing_entries = loader.load_file(existing)[0] if existing else []

    extracted = []
    for filename in _walk(src, log):
        with errors:
            importer = identify.identify(ctx.importers, filename)
            if not importer:
                log('')  # Newline.
                continue

            # Signal processing of this document.
            log(' ...', nl=False)

            # Extract entries.
            entries = extract.extract_from_file(importer, filename,
                                                existing_entries)
            extracted.append((filename, entries))
            log(' OK', fg='green')

        if failfast and errors:
            break

    # Invoke hooks.
    hooks = [extract.find_duplicate_entries
             ] if ctx.hooks is None else ctx.hooks
    for func in hooks:
        extracted = func(extracted, existing_entries)

    # Reverse sort order, if requested.
    if reverse:
        for filename, entries in extracted:
            entries.reverse()

    # Serialize entries.
    extract.print_extracted_entries(extracted, output)

    if errors:
        sys.exit(1)
Ejemplo n.º 3
0
def _run(ctx,
         documents: List[str],
         expected: str,
         verbose: int,
         quiet: int,
         generate: bool = False,
         failfast: bool = False,
         force: bool = False):
    """Implement the test and generate commands."""

    assert len(ctx.importers) == 1
    importer = ctx.importers[0]

    verbosity = verbose - quiet
    log = utils.logger(verbosity)
    failures = 0

    for doc in utils.walk(documents):
        if doc.endswith('.beancount'):
            continue

        # Unless verbose mode is enabled, do not output a newline so
        # the test result is printed on the same line as the test
        # document filename.
        log(f'* {doc}', nl=verbosity > 0)

        # Compute the path to the expected output file.
        expected_filename = f"{doc}.beancount"
        if expected:
            expected_filename = path.join(expected,
                                          path.basename(expected_filename))

        # Run the importer's identify() method.
        if importer.identify(doc):
            account, date, name, entries = run_importer(importer, doc)
            log(f'  {expected_filename}', 1)
            if account is None:
                failures += 1
                log('  ERROR', fg='red')
                log('  ValueError: account() should not return None')
                continue
            log(
                '  {}/{:%Y-%m-%d}-{}'.format(account.replace(":", "/"), date
                                             or utils.getmdate(doc), name
                                             or path.basename(doc)), 1)
            if generate:
                try:
                    write_expected_file(expected_filename,
                                        account,
                                        date,
                                        name,
                                        entries,
                                        force=force)
                except FileExistsError as ex:
                    failures += 1
                    log('  ERROR', fg='red')
                    log('  FileExistsError: {}'.format(ex.filename))
                    continue
                log('  OK', fg='green')
                continue
            try:
                diff = compare_expected(expected_filename, account, date, name,
                                        entries)
            except FileNotFoundError:
                # The importer has positively identified a document
                # for which there is no expecred output file.
                failures += 1
                log('  ERROR', fg='red')
                log('  ExpectedOutputFileNotFound')
                continue
            if diff:
                # Test failure. Log an error.
                failures += 1
                log('  ERROR', fg='red')
                if verbosity >= 0:
                    sys.stdout.writelines(diff)
                    sys.stdout.write(os.linesep)
                    continue
            log('  PASSED', fg='green')

        elif path.exists(expected_filename):
            # The importer has not identified a document it should have.
            failures += 1
            log('  ERROR', fg='red')
            log('  DocumentNotIdentified')

        else:
            # Ignore files that are not positively identified by the
            # importer and for which there is no expected output file.
            log('  IGNORED')

        if failfast and failures:
            break

    if failures:
        sys.exit(1)
Ejemplo n.º 4
0
 def test_logger(self):
     logger = utils.logger()
     self.assertIsInstance(logger, types.FunctionType)
     logger = utils.logger(logging.INFO, err=True)
     self.assertIsInstance(logger, types.FunctionType)
Ejemplo n.º 5
0
def _archive(ctx, src, destination, dry_run, overwrite, failfast):
    """Archive documents.

    Walk the SRC list of files or directories and move each file
    identified by one of the configured importers in a directory
    hierarchy mirroring the structure of the accounts associated to
    the documents and with a file name composed by the document date
    and document name returned by the importer.

    Documents are moved to their filing location only when no errors
    are encountered processing all the input files.  Documents in the
    destination directory are not overwritten, unless the --force
    option is used.  When the directory hierarchy root is not
    specified with the --destination DIR options, it is assumed to be
    directory in which the ingest script is located.

    """
    # If the output directory is not specified, move the files at the
    # root where the import script is located. Providing this default
    # seems better than using a required option.
    if destination is None:
        import __main__
        destination = os.path.dirname(os.path.abspath(__main__.__file__))

    log = utils.logger()
    errors = exceptions.ExceptionsTrap(log)
    renames = []

    for filename in _walk(src, log):
        with errors:
            importer = identify.identify(ctx.importers, filename)
            if not importer:
                log('')  # Newline.
                continue

            # Signal processing of this document.
            log(' ...', nl=False)

            destpath = archive.filepath(importer, filename)

            # Prepend destination directory path.
            destpath = os.path.join(destination, destpath)

            # Check for destination filename collisions.
            collisions = [src for src, dst in renames if dst == destpath]
            if collisions:
                raise exceptions.Error('Collision in destination file path.',
                                       destpath)

            # Check if the destination file already exists.
            if not overwrite and os.path.exists(destpath):
                raise exceptions.Error('Destination file already exists.',
                                       destpath)

            renames.append((filename, destpath))
            log(' OK', fg='green')
            log(f'  {destpath:}')

        if failfast and errors:
            break

    # If there are any errors, stop here.
    if errors:
        log('# Errors detected: documents will not be filed.')
        sys.exit(1)

    if not dry_run:
        for src, dst in renames:
            archive.move(src, dst)