"""meta_project - Validate project metadata"""
# Copyright © 2020 Brett Smith
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from pathlib import Path
import yaml
import yaml.error
from . import core
from .. import config as configmod
from .. import data
from .. import errors as errormod
from ..beancount_types import (
MetaValueEnum,
Transaction,
)
from typing import (
Any,
Dict,
NoReturn,
Optional,
Set,
)
class MetaProject(core._NormalizePostingMetadataHook):
DEFAULT_PROJECT = 'Conservancy'
PROJECT_DATA_PATH = Path('Projects', 'project-data.yml')
VALUES_ENUM = core.MetadataEnum('project', {DEFAULT_PROJECT})
def __init__(self, config: configmod.Config, source_path: Path=PROJECT_DATA_PATH) -> None:
repo_path = config.repository_path()
if repo_path is None:
self._config_error("no repository configured")
project_data_path = repo_path / source_path
source = {'filename': str(project_data_path)}
try:
with project_data_path.open() as yaml_file:
project_data: Dict[str, Dict[str, Any]] = yaml.safe_load(yaml_file)
names: Set[MetaValueEnum] = {self.DEFAULT_PROJECT}
aliases: Dict[MetaValueEnum, MetaValueEnum] = {}
for key, params in project_data.items():
name = params.get('accountName', key)
names.add(name)
human_name = params.get('humanName', name)
if name != human_name:
aliases[human_name] = name
if name != key:
aliases[key] = name
except AttributeError:
self._config_error("loaded YAML data not in project-data format", project_data_path)
except OSError as error:
self._config_error(error.strerror, project_data_path)
except yaml.error.YAMLError as error:
self._config_error(error.args[0] or "YAML load error", project_data_path)
else:
self.VALUES_ENUM = core.MetadataEnum(self.METADATA_KEY, names, aliases)
def _config_error(self, msg: str, filename: Optional[Path]=None) -> NoReturn:
source = {}
if filename is not None:
source['filename'] = str(filename)
raise errormod.ConfigurationError(
"cannot load project data: " + msg,
source=source,
)
def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
return post.account.is_under('Assets', 'Liabilities') is None
def _default_value(self, txn: Transaction, post: data.Posting) -> MetaValueEnum:
if post.account.is_under(
'Accrued:VacationPayable',
'Expenses:Payroll',
):
return self.DEFAULT_PROJECT
else:
raise errormod.InvalidMetadataError(txn, self.METADATA_KEY, None, post)