def read_details(filename): """Read the encrypted details file and create an index of its contents. Args: filename: A string, the name of the filename to read. Returns: A pair of the Details object containing the database and a mapping of Beancount account name to its (institution, account) pair. """ # Read the encrypted details file. if encryption.is_encrypted_file(filename): contents = encryption.read_encrypted_file(filename) else: with open(args.details_filename, 'rb') as infile: contents = infile.read() # Read the ASCII protobuf database. details = assets_pb2.Details() text_format.Merge(contents, details) # Create a mapping of account name to (institution, account) pairs. mapping = {} for institution in details.institution: for acc in institution.account: assert acc.beancount not in mapping, ( "Account name {} is not unique!".format(acc.beancount)) mapping[acc.beancount] = (institution, acc) return details, mapping
def __init__(self, path): #: The path to the main Beancount file. self.beancount_file_path = path self._is_encrypted = is_encrypted_file(path) self._filters = { 'account': AccountFilter(), 'from': FromFilter(), 'payee': PayeeFilter(), 'tag': TagFilter(), 'time': TimeFilter(), } #: An :class:`AttributesModule` instance. self.attributes = AttributesModule(self) #: A :class:`.BudgetModule` instance. self.budgets = BudgetModule(self) #: A :class:`.ChartModule` instance. self.charts = ChartModule(self) #: A :class:`.ExtensionModule` instance. self.extensions = ExtensionModule(self) #: A :class:`.FileModule` instance. self.file = FileModule(self) #: A :class:`.IngestModule` instance. self.ingest = IngestModule(self) #: A :class:`.FavaMisc` instance. self.misc = FavaMisc(self) #: A :class:`.QueryShell` instance. self.query_shell = QueryShell(self) self._watcher = Watcher() #: List of all (unfiltered) entries. self.all_entries = None #: Dict of list of all (unfiltered) entries by type. self.all_entries_by_type = None #: A list of all errors reported by Beancount. self.errors = None #: A Beancount options map. self.options = None #: A Namedtuple containing the names of the five base accounts. self.account_types = None #: A dict containing information about the accounts. self.accounts = _AccountDict() #: A dict with all of Fava's option values. self.fava_options = None self.load_file()
def __init__(self, path: str) -> None: #: The path to the main Beancount file. self.beancount_file_path = path self._is_encrypted = is_encrypted_file(path) #: An :class:`AttributesModule` instance. self.attributes = AttributesModule(self) #: A :class:`.BudgetModule` instance. self.budgets = BudgetModule(self) #: A :class:`.ChartModule` instance. self.charts = ChartModule(self) #: A :class:`.ExtensionModule` instance. self.extensions = ExtensionModule(self) #: A :class:`.FileModule` instance. self.file = FileModule(self) #: A :class:`.IngestModule` instance. self.ingest = IngestModule(self) #: A :class:`.FavaMisc` instance. self.misc = FavaMisc(self) #: A :class:`.DecimalFormatModule` instance. self.format_decimal = DecimalFormatModule(self) #: A :class:`.QueryShell` instance. self.query_shell = QueryShell(self) self._watcher = Watcher() #: List of all (unfiltered) entries. self.all_entries = [] #: Dict of list of all (unfiltered) entries by type. self.all_entries_by_type = group_entries_by_type([]) #: A list of all errors reported by Beancount. self.errors: list[BeancountError] = [] #: A Beancount options map. self.options: BeancountOptions = OPTIONS_DEFAULTS #: A dict containing information about the accounts. self.accounts = AccountDict() #: A dict with commodity names (from the 'name' metadata) self.commodity_names: dict[str, str] = {} #: A dict with all of Fava's option values. self.fava_options: FavaOptions = FavaOptions() self._date_first: datetime.date | None = None self._date_last: datetime.date | None = None self.load_file()
def __init__(self, path): #: The path to the main Beancount file. self.beancount_file_path = path self._is_encrypted = is_encrypted_file(path) self._filters = {} #: An :class:`AttributesModule` instance. self.attributes = AttributesModule(self) #: A :class:`.BudgetModule` instance. self.budgets = BudgetModule(self) #: A :class:`.ChartModule` instance. self.charts = ChartModule(self) #: A :class:`.ExtensionModule` instance. self.extensions = ExtensionModule(self) #: A :class:`.FileModule` instance. self.file = FileModule(self) #: A :class:`.IngestModule` instance. self.ingest = IngestModule(self) #: A :class:`.FavaMisc` instance. self.misc = FavaMisc(self) #: A :class:`.DecimalFormatModule` instance. self.format_decimal = DecimalFormatModule(self) #: A :class:`.QueryShell` instance. self.query_shell = QueryShell(self) self._watcher = Watcher() #: List of all (unfiltered) entries. self.all_entries = None #: Dict of list of all (unfiltered) entries by type. self.all_entries_by_type = None #: A list of all errors reported by Beancount. self.errors = None #: A Beancount options map. self.options = None #: A Namedtuple containing the names of the five base accounts. self.account_types = None #: A dict containing information about the accounts. self.accounts = _AccountDict() #: A dict with all of Fava's option values. self.fava_options = None self.load_file()
def __init__(self, path: str) -> None: #: The path to the main Beancount file. self.beancount_file_path = path self._is_encrypted = is_encrypted_file(path) #: An :class:`AttributesModule` instance. self.attributes = AttributesModule(self) #: A :class:`.BudgetModule` instance. self.budgets = BudgetModule(self) #: A :class:`.ChartModule` instance. self.charts = ChartModule(self) #: A :class:`.ExtensionModule` instance. self.extensions = ExtensionModule(self) #: A :class:`.FileModule` instance. self.file = FileModule(self) #: A :class:`.IngestModule` instance. self.ingest = IngestModule(self) #: A :class:`.FavaMisc` instance. self.misc = FavaMisc(self) #: A :class:`.DecimalFormatModule` instance. self.format_decimal = DecimalFormatModule(self) #: A :class:`.QueryShell` instance. self.query_shell = QueryShell(self) self._watcher = Watcher() #: List of all (unfiltered) entries. self.all_entries = [] #: Dict of list of all (unfiltered) entries by type. self.all_entries_by_type: Dict[Type[Directive], Entries] = {} #: A list of all errors reported by Beancount. self.errors: List[BeancountError] = [] #: A Beancount options map. self.options: Dict[str, Any] = {} #: A dict containing information about the accounts. self.accounts = AccountDict() #: A dict containing information about the commodities self.commodities: Dict[str, Commodity] = {} #: A dict with all of Fava's option values. self.fava_options: FavaOptions = {} self.load_file()
def test_is_encrypted_file(self): with tempfile.NamedTemporaryFile(suffix='.txt') as file: file.write(b'\n') file.flush() self.assertFalse(encryption.is_encrypted_file(file.name)) with tempfile.NamedTemporaryFile(suffix='.gpg') as file: file.flush() self.assertTrue(encryption.is_encrypted_file(file.name)) with tempfile.NamedTemporaryFile(suffix='.asc') as file: file.write(b'Anything else\n') file.flush() self.assertFalse(encryption.is_encrypted_file(file.name)) with tempfile.NamedTemporaryFile(suffix='.asc') as file: file.write(b'\n\n\n') file.write(b'-----BEGIN PGP MESSAGE-----\n') file.write(b'\n\n\n') file.flush() self.assertTrue(encryption.is_encrypted_file(file.name))
def __init__(self, beancount_file_path): self.beancount_file_path = beancount_file_path self.is_encrypted = encryption.is_encrypted_file(beancount_file_path) self.filters = { "account": AccountFilter(), "from": FromFilter(), "payee": PayeeFilter(), "tag": TagFilter(), "time": TimeFilter(), } self.watcher = Watcher() self.load_file()
def __init__(self, beancount_file_path): self.beancount_file_path = beancount_file_path self.is_encrypted = encryption.is_encrypted_file(beancount_file_path) self.filters = { 'account': AccountFilter(), 'from': FromFilter(), 'payee': PayeeFilter(), 'tag': TagFilter(), 'time': TimeFilter(), } self.charts = Charts(self) self.watcher = Watcher() self.load_file()
def load_file(filename, log_timings=None, log_errors=None, extra_validations=None, encoding=None): """Open a Beancount input file, parse it, run transformations and validate. Args: filename: The name of the file to be parsed. log_timings: A file object or function to write timings to, or None, if it should remain quiet. (Note that this is intended to use the logging methods and does not insert a newline.) log_errors: A file object or function to write errors to, or None, if it should remain quiet. extra_validations: A list of extra validation functions to run after loading this list of entries. encoding: A string or None, the encoding to decode the input filename with. Returns: A triple of (entries, errors, option_map) where "entries" is a date-sorted list of entries from the file, "errors" a list of error objects generated while parsing and validating the file, and "options_map", a dict of the options parsed from the file. """ filename = path.expandvars(path.expanduser(filename)) if not path.isabs(filename): filename = path.normpath(path.join(os.getcwd(), filename)) if encryption.is_encrypted_file(filename): # Note: Caching is not supported for encrypted files. entries, errors, options_map = load_encrypted_file( filename, log_timings, log_errors, extra_validations, False, encoding) else: entries, errors, options_map = _load_file(filename, log_timings, extra_validations, encoding) _log_errors(errors, log_errors) return entries, errors, options_map
def _parse_recursive(sources, log_timings, encoding=None): """Parse Beancount input, run its transformations and validate it. Recursively parse a list of files or strings and their include files and return an aggregate of parsed directives, errors, and the top-level options-map. If the same file is being parsed twice, ignore it and issue an error. Args: sources: A list of (filename-or-string, is-filename) where the first element is a string, with either a filename or a string to be parsed directly, and the second argument is a boolean that is true if the first is a filename. You may provide a list of such arguments to be parsed. Filenames must be absolute paths. log_timings: A function to write timings to, or None, if it should remain quiet. encoding: A string or None, the encoding to decode the input filename with. Returns: A tuple of (entries, parse_errors, options_map). """ assert isinstance(sources, list) and all( isinstance(el, tuple) for el in sources) # Current parse state. entries, parse_errors = [], [] options_map = None # A stack of sources to be parsed. source_stack = list(sources) # A list of absolute filenames that have been parsed in the past, used to # detect and avoid duplicates (cycles). filenames_seen = set() with misc_utils.log_time('beancount.parser.parser', log_timings, indent=1): while source_stack: source, is_file = source_stack.pop(0) is_top_level = options_map is None # If the file is encrypted, read it in and process it as a string. if is_file: cwd = path.dirname(source) source_filename = source if encryption.is_encrypted_file(source): source = encryption.read_encrypted_file(source) is_file = False else: # If we're parsing a string, the CWD is the current process # working directory. cwd = os.getcwd() source_filename = None if is_file: # All filenames here must be absolute. assert path.isabs(source) filename = path.normpath(source) # Check for file previously parsed... detect duplicates. if filename in filenames_seen: parse_errors.append( LoadError( data.new_metadata("<load>", 0), 'Duplicate filename parsed: "{}"'.format(filename), None)) continue # Check for a file that does not exist. if not path.exists(filename): parse_errors.append( LoadError(data.new_metadata("<load>", 0), 'File "{}" does not exist'.format(filename), None)) continue # Parse a file from disk directly. filenames_seen.add(filename) with misc_utils.log_time('beancount.parser.parser.parse_file', log_timings, indent=2): (src_entries, src_errors, src_options_map) = parser.parse_file(filename, encoding=encoding) cwd = path.dirname(filename) else: # Encode the contents if necessary. if encoding: if isinstance(source, bytes): source = source.decode(encoding) source = source.encode('ascii', 'replace') # Parse a string buffer from memory. with misc_utils.log_time( 'beancount.parser.parser.parse_string', log_timings, indent=2): (src_entries, src_errors, src_options_map) = parser.parse_string( source, source_filename) # Merge the entries resulting from the parsed file. entries.extend(src_entries) parse_errors.extend(src_errors) # We need the options from the very top file only (the very # first file being processed). No merging of options should # occur. if is_top_level: options_map = src_options_map else: aggregate_options_map(options_map, src_options_map) # Add includes to the list of sources to process. chdir() for glob, # which uses it indirectly. include_expanded = [] with file_utils.chdir(cwd): for include_filename in src_options_map['include']: matched_filenames = glob.glob(include_filename, recursive=True) if matched_filenames: include_expanded.extend(matched_filenames) else: parse_errors.append( LoadError( data.new_metadata("<load>", 0), 'File glob "{}" does not match any files'. format(include_filename), None)) for include_filename in include_expanded: if not path.isabs(include_filename): include_filename = path.join(cwd, include_filename) include_filename = path.normpath(include_filename) # Add the include filenames to be processed later. source_stack.append((include_filename, True)) # Make sure we have at least a dict of valid options. if options_map is None: options_map = options.OPTIONS_DEFAULTS.copy() # Save the set of parsed filenames in options_map. options_map['include'] = sorted(filenames_seen) return entries, parse_errors, options_map
def __init__(self, path): s3_file_backend = S3FileBackend(self, path) #: The path to the main Beancount file. self.beancount_file_path = path if s3_file_backend.active: self.beancount_file_path = s3_file_backend.beancount_file_path self._is_encrypted = is_encrypted_file(path) self._filters = {} #: An :class:`AttributesModule` instance. self.attributes = AttributesModule(self) #: A :class:`.BudgetModule` instance. self.budgets = BudgetModule(self) #: A :class:`.ChartModule` instance. self.charts = ChartModule(self) #: A :class:`.ExtensionModule` instance. self.extensions = ExtensionModule(self) #: A :class:`.FileModule` instance. self.file = FileModule(self) if s3_file_backend.active: self.file = s3_file_backend #: A :class:`.IngestModule` instance. self.ingest = IngestModule(self) #: A :class:`.FavaMisc` instance. self.misc = FavaMisc(self) #: A :class:`.DecimalFormatModule` instance. self.format_decimal = DecimalFormatModule(self) #: A :class:`.QueryShell` instance. self.query_shell = QueryShell(self) self._watcher = Watcher() #: List of all (unfiltered) entries. self.all_entries = None #: Dict of list of all (unfiltered) entries by type. self.all_entries_by_type = None #: A list of all errors reported by Beancount. self.errors = None #: A Beancount options map. self.options = None #: A Namedtuple containing the names of the five base accounts. self.account_types = None #: A dict containing information about the accounts. self.accounts = _AccountDict() #: A dict with all of Fava's option values. self.fava_options = None self.load_file()