From 6bec182703885761699f6d53bc9034933b03197e Mon Sep 17 00:00:00 2001 From: David Luevano Alvarado Date: Sun, 17 Apr 2022 19:39:38 -0600 Subject: add initial logging capabilities still need to add logging to rest of the program: builder, database, discovery, page, parser and rest of the pyssg (build part) --- README.md | 1 + src/pyssg/__init__.py | 17 ++++++++++++++++- src/pyssg/__main__.py | 2 ++ src/pyssg/configuration.py | 14 ++++++++++++-- src/pyssg/per_level_formatter.py | 28 ++++++++++++++++++++++++++++ src/pyssg/pyssg.py | 26 ++++++++++++++++++++------ src/pyssg/utils.py | 15 ++++++++++----- 7 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 src/pyssg/per_level_formatter.py diff --git a/README.md b/README.md index 7845805..2163a95 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Inspired (initially) by Roman Zolotarev's [`ssg5`](https://rgz.ee/bin/ssg5) and - [x] Avoid the program to freak out when there are directories created in advance. - [ ] Provide more meaningful error messages when you are missing mandatory tags in your `.md` files. - [ ] More complex directory structure to support multiple subdomains and different types of pages. +- [ ] Add option/change to using an SQL database instead of the custom solution. ### Markdown features diff --git a/src/pyssg/__init__.py b/src/pyssg/__init__.py index 074ae72..cdd4cd1 100644 --- a/src/pyssg/__init__.py +++ b/src/pyssg/__init__.py @@ -1,4 +1,19 @@ from .pyssg import main +import logging +from logging import Logger, StreamHandler +from .per_level_formatter import PerLevelFormatter -__all__ = ['main'] +# since this is the root package, setup the logger here, +# set DEBUG here for testing purposes, can't make it +# dynamic yet (with a flag, for example) +__LOG_LEVEL: int = logging.INFO +log: Logger = logging.getLogger(__name__) +log.setLevel(__LOG_LEVEL) +ch: StreamHandler = StreamHandler() +ch.setLevel(__LOG_LEVEL) +ch.setFormatter(PerLevelFormatter()) +log.addHandler(ch) + +# not meant to be used as a package, so just give main +__all__ = ['main'] \ No newline at end of file diff --git a/src/pyssg/__main__.py b/src/pyssg/__main__.py index 9ed4d74..5213889 100644 --- a/src/pyssg/__main__.py +++ b/src/pyssg/__main__.py @@ -1,5 +1,7 @@ from .pyssg import main +# since this is not used as a package, rather it's used as a command line tool, +# this is never called because pyssg:main is called directly when running pyssg if __name__ == '__main__': main() diff --git a/src/pyssg/configuration.py b/src/pyssg/configuration.py index dd6bfaa..a721dba 100644 --- a/src/pyssg/configuration.py +++ b/src/pyssg/configuration.py @@ -3,6 +3,10 @@ from importlib.metadata import version from importlib.resources import path as rpath from datetime import datetime, timezone from configparser import ConfigParser +import logging +from logging import Logger + +log: Logger = logging.getLogger(__name__) DEFAULT_CONFIG_PATH = '$XDG_CONFIG_HOME/pyssg/config.ini' @@ -12,25 +16,31 @@ VERSION = version('pyssg') def __check_well_formed_config(config: ConfigParser) -> None: default_config: ConfigParser = ConfigParser() with rpath('pyssg.plt', 'default.ini') as p: + log.debug('reading config file "%s"', p) default_config.read(p) for section in default_config.sections(): + log.debug('checking section "%s"', section) if not config.has_section(section): - print(f'config does not have section "{section}"') + log.error('config does not have section "%s"', section) sys.exit(1) for option in default_config.options(section): + log.debug('checking option "%s"', option) if not config.has_option(section, option): - print(f'config does not have option "{option}" in section "{section}"') + log.error('config does not have option "%s" in section "%s"', option, section) sys.exit(1) def get_parsed_config(path: str) -> ConfigParser: config: ConfigParser = ConfigParser() + log.debug('reading config file "%s"', path) config.read(path) + log.debug('checking that config file is well formed') __check_well_formed_config(config) # set other required options + log.debug('setting extra config options') config.set('fmt', 'rss_date', '%%a, %%d %%b %%Y %%H:%%M:%%S GMT') config.set('fmt', 'sitemap_date', '%%Y-%%m-%%d') config.set('info', 'version', VERSION) diff --git a/src/pyssg/per_level_formatter.py b/src/pyssg/per_level_formatter.py new file mode 100644 index 0000000..2010483 --- /dev/null +++ b/src/pyssg/per_level_formatter.py @@ -0,0 +1,28 @@ +import logging +from logging import Formatter + + +class PerLevelFormatter(logging.Formatter): + # colors for the terminal in ansi + yellow = "\x1b[33m" + red = "\x1b[31m" + bold_red = "\x1b[31;1m" + reset = "\x1b[0m" + + DATE_FMT = '%Y-%m-%d %H:%M:%S' + COMMON_FMT = '[%(levelname)s] [%(module)s:%(funcName)s:%(lineno)d]: %(message)s' + FORMATS = { + logging.DEBUG: COMMON_FMT, + logging.INFO: '%(message)s', + logging.WARNING: f'{yellow}{COMMON_FMT}{reset}', + logging.ERROR: f'{red}{COMMON_FMT}{reset}', + logging.CRITICAL: f'{bold_red}{COMMON_FMT}{reset}' + } + + + def format(self, record: str) -> str: + fmt: str = self.FORMATS.get(record.levelno) + formatter: Formatter = logging.Formatter( + fmt=fmt, datefmt=self.DATE_FMT, style='%') + + return formatter.format(record) diff --git a/src/pyssg/pyssg.py b/src/pyssg/pyssg.py index 9b82231..958b397 100644 --- a/src/pyssg/pyssg.py +++ b/src/pyssg/pyssg.py @@ -1,5 +1,7 @@ import os import sys +import logging +from logging import Logger, StreamHandler from importlib.resources import path as rpath from typing import Union from configparser import ConfigParser @@ -10,43 +12,53 @@ from yafg import YafgExtension from MarkdownHighlight.highlight import HighlightExtension from markdown_checklist.extension import ChecklistExtension +from .per_level_formatter import PerLevelFormatter from .utils import create_dir, copy_file, sanity_check_path from .arg_parser import get_parsed_arguments from .configuration import get_parsed_config, DEFAULT_CONFIG_PATH, VERSION from .database import Database from .builder import Builder +log: Logger = logging.getLogger(__name__) + def main() -> None: args: dict[str, Union[str, bool]] = vars(get_parsed_arguments()) + if not len(sys.argv) > 1: - print(f'pyssg v{VERSION} - no arguments passed, --help for more') + log.info('pyssg v%s - no arguments passed, --help for more', VERSION) sys.exit(0) if args['version']: - print(f'pyssg v{VERSION}') + log.info('pyssg v%s', VERSION) sys.exit(0) + log.debug('checking config file path') config_path: str = args['config'] if args['config'] else DEFAULT_CONFIG_PATH config_path = os.path.normpath(os.path.expandvars(config_path)) sanity_check_path(config_path) config_dir, _ = os.path.split(config_path) + log.debug('checked config file path, final config path "%s"', config_path) if args['copy_default_config']: + log.info('copying default config file') create_dir(config_dir) with rpath('pyssg.plt', 'default.ini') as p: copy_file(p, config_path) sys.exit(0) if not os.path.exists(config_path): - print(f'''config file does't exist in path "{config_path}"; make sure - the path is correct; use --copy-default-config to if you - haven't already''') + log.error('config file does\'t exist in path "%s"; make sure' + ' the path is correct; use --copy-default-config if it\'s the' + ' first time if you haven\'t already', config_path) sys.exit(1) + log.debug('parsing config file') config: ConfigParser = get_parsed_config(config_path) + log.debug('parsed config file') if args['init']: + log.info('initializing the directory structure and copying over templates') create_dir(config.get('path', 'src')) create_dir(os.path.join(config.get('path', 'dst'), 'tag'), True) create_dir(config.get('path', 'plt')) @@ -55,12 +67,15 @@ def main() -> None: 'tag.html', 'rss.xml', 'sitemap.xml') + log.debug('list of files to copy over: (%s)', ', '.join(files)) for f in files: plt_file: str = os.path.join(config.get('path', 'plt'), f) with rpath('pyssg.plt', f) as p: copy_file(p, plt_file) sys.exit(0) + # TODO: add logging to all of the build part, that includes the builder, + # database, discovery, page and parser if args['build']: # start the db db: Database = Database(os.path.join(config.get('path', 'src'), '.files')) @@ -73,7 +88,6 @@ def main() -> None: trim_blocks=True, lstrip_blocks=True) - # md extensions exts: list = ['extra', 'meta', diff --git a/src/pyssg/utils.py b/src/pyssg/utils.py index 8e5d90e..2194fe1 100644 --- a/src/pyssg/utils.py +++ b/src/pyssg/utils.py @@ -1,6 +1,10 @@ import os import sys import shutil +import logging +from logging import Logger + +log: Logger = logging.getLogger(__name__) def create_dir(path: str, p: bool=False) -> None: @@ -9,20 +13,21 @@ def create_dir(path: str, p: bool=False) -> None: os.makedirs(path) else: os.mkdir(path) - print(f'created directory "{path}"') + log.info('created directory "%s"', path) except FileExistsError: - print(f'directory "{path}" already exists') + log.info('directory "%s" already exists, ignoring', path) def copy_file(src: str, dst: str) -> None: if not os.path.exists(dst): shutil.copy(src, dst) - print(f'copied file "{src}" to "{dst}"') + log.info('copied file "%s" to "%s"', src, dst) else: - print(f'"{dst}" already exists') + log.info('file "%s" already exists, ignoring', dst) def sanity_check_path(path: str) -> None: if '$' in path: - print(f'"$" character found in path: "{path}"; could be due to non-existant env var.') + log.error('"$" character found in path "%s";' + ' could be due to non-existant env var.', path) sys.exit(1) -- cgit v1.2.3-54-g00ecf