Skip to content

Commit

Permalink
refactor: rework entry point of the application and add logger (#79)
Browse files Browse the repository at this point in the history
  • Loading branch information
esynr3z committed Oct 14, 2024
1 parent 206f228 commit 3585e44
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 379 deletions.
218 changes: 10 additions & 208 deletions corsair/__main__.py
Original file line number Diff line number Diff line change
@@ -1,208 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""Run Corsair from command line with arguments.
"""

import sys
import os
import argparse
from pathlib import Path
import corsair
from . import utils
from contextlib import contextmanager
import importlib.util

__all__ = ['main']


@contextmanager
def cwd(path):
oldpwd = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(oldpwd)


class ArgumentParser(argparse.ArgumentParser):
"""Inherit ArgumentParser to override the behaviour of error method."""

def error(self, message):
self.print_help(sys.stderr)
self.exit(2, '\n%s: error: %s\n' % (self.prog, message))


def parse_arguments():
"""Parse and validate arguments."""
parser = ArgumentParser(prog=corsair.__title__,
description=corsair.__description__)
parser.add_argument('-v', '--version',
action='version',
version='%(prog)s v' + corsair.__version__)
parser.add_argument(metavar='WORKDIR',
nargs='?',
dest='workdir_path',
default=os.getcwd(),
help='working directory (default is the current directory)')
parser.add_argument('-r',
metavar='REGMAP',
dest='regmap_path',
help='read register map from file')
parser.add_argument('-c',
metavar='CONFIG',
dest='config_path',
help='read configuration from file')
template_choices = ['json', 'yaml', 'txt']
parser.add_argument('-t',
metavar='FORMAT',
choices=template_choices,
dest='template_format',
help='create templates (choose from %s)' % ', '.join(template_choices))
return parser.parse_args()


def generate_templates(format):
print("... templates format: '%s'" % format)
# global configuration
globcfg = corsair.config.default_globcfg()
globcfg['data_width'] = 32
globcfg['address_width'] = 16
globcfg['register_reset'] = 'sync_pos'
corsair.config.set_globcfg(globcfg)

# targets
targets = {}
targets.update(corsair.generators.Verilog(path="hw/regs.v").make_target('v_module'))
targets.update(corsair.generators.Vhdl(path="hw/regs.vhd").make_target('vhdl_module'))
targets.update(corsair.generators.VerilogHeader(path="hw/regs.vh").make_target('v_header'))
targets.update(corsair.generators.SystemVerilogPackage(path="hw/regs_pkg.sv").make_target('sv_pkg'))
targets.update(corsair.generators.Python(path="sw/regs.py").make_target('py'))
targets.update(corsair.generators.CHeader(path="sw/regs.h").make_target('c_header'))
targets.update(corsair.generators.Markdown(path="doc/regs.md", image_dir="md_img").make_target('md_doc'))
targets.update(corsair.generators.Asciidoc(path="doc/regs.adoc", image_dir="adoc_img").make_target('asciidoc_doc'))

# create templates
if format == 'txt':
rmap = corsair.utils.create_template_simple()
else:
rmap = corsair.utils.create_template()
# register map template
if format == 'json':
gen = corsair.generators.Json(rmap)
regmap_path = 'regs.json'
elif format == 'yaml':
gen = corsair.generators.Yaml(rmap)
regmap_path = 'regs.yaml'
elif format == 'txt':
gen = corsair.generators.Txt(rmap)
regmap_path = 'regs.txt'
print("... generate register map file '%s'" % regmap_path)
gen.generate()
# configuration file template
config_path = 'csrconfig'
globcfg['regmap_path'] = regmap_path
print("... generate configuration file '%s'" % config_path)
corsair.config.write_csrconfig(config_path, globcfg, targets)


def die(msg):
print("Error: %s" % msg)
exit(1)


def finish():
print('Success!')
exit(0)


def app(args):
print("... set working directory '%s'" % args.workdir_path)

# check if teplates are needed
if args.template_format:
generate_templates(args.template_format)
finish()

# check if configuration file path was provided
if args.config_path:
config_path = Path(args.config_path)
else:
config_path = Path('csrconfig')
# check it existance
if not config_path.is_file():
die("Can't find configuration file '%s'!" % config_path)
# try to read it
print("... read configuration file '%s'" % config_path)
globcfg, targets = corsair.config.read_csrconfig(config_path)

# check if regiter map file path was provided
if args.regmap_path:
regmap_path = Path(args.regmap_path)
globcfg['regmap_path'] = regmap_path
elif 'regmap_path' in globcfg.keys():
regmap_path = Path(globcfg['regmap_path'])
else:
regmap_path = None
print("Warning: No register map file was specified!")
corsair.config.set_globcfg(globcfg)

if regmap_path:
# check it existance
if not regmap_path.is_file():
die("Can't find register map file '%s'!" % regmap_path)
# try to read it
print("... read register map file '%s'" % regmap_path)
rmap = corsair.RegisterMap()
rmap.read_file(regmap_path)
print("... validate register map")
rmap.validate()
else:
rmap = None

# make targets
if not targets:
die("No targets were specified! Nothing to do!")
for t in targets:
print("... make '%s': " % t, end='')
if 'generator' not in targets[t].keys():
die("No generator was specified for the target!")

if '.py::' in targets[t]['generator']:
custom_module_path, custom_generator_name = targets[t]['generator'].split('::')
custom_module_name = utils.get_file_name(custom_module_path)
spec = importlib.util.spec_from_file_location(custom_module_name, custom_module_path)
custom_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(custom_module)
try:
gen_obj = getattr(custom_module, custom_generator_name)
gen_name = custom_generator_name
except AttributeError:
die("Generator '%s' from module '%s' does not exist!" % (custom_generator_name, custom_module_path))
else:
gen_name = targets[t]['generator']
try:
gen_obj = getattr(corsair.generators, gen_name)
except AttributeError:
die("Generator '%s' does not exist!" % gen_name)

gen_args = targets[t]
print("%s -> '%s': " % (gen_name, gen_args['path']))
gen_obj(rmap, **gen_args).generate()


def main():
"""Program main"""
# parse arguments
args = parse_arguments()

# do all the things inside working directory
args.workdir_path = str(Path(args.workdir_path).absolute())
with cwd(args.workdir_path):
app(args)
finish()


if __name__ == '__main__':
main()
#!/usr/bin/env python3

"""With this Corsair can be run as a module."""

from __future__ import annotations

from . import app

if __name__ == "__main__":
app.main()
66 changes: 66 additions & 0 deletions corsair/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Main code of the Corsair application."""

from __future__ import annotations

import sys

from . import log, utils, version
from .cli import Args, parse_args

logger = log.get_logger()

Dummy = int


def main() -> None:
"""Entry point of the application."""
try:
app(parse_args())
except Exception:
logger.exception("Application finished with error. See the exception description below.")
sys.exit(1)


def app(args: Args) -> None:
"""Application body."""
log.set_debug(args.debug)
log.set_color(not args.no_color)

logger.debug("corsair v%s", version.__version__)
logger.debug("args=%s", args)

with utils.chdir(args.workdir):
logger.info("Working directory is '%s'", args.workdir)
if args.init_project:
create_project(args)
else:
globcfg, targets = read_config(args)
regmap = read_regmap(args, globcfg)
make_targets(args, globcfg, targets, regmap)


def create_project(args: Args) -> None:
"""Create a simple project, which can be used as a template for user."""
logger.info("Start creating simple %s project ...", args.init_project)


def read_config(args: Args) -> tuple[Dummy, list[Dummy]]:
"""Read configuration file."""
logger.debug("args=%s", args) # TODO: remove when argument is used below
logger.info("Start reading configuration file ...")
raise NotImplementedError("read_config() is not implemented!")


def read_regmap(args: Args, globcfg: Dummy) -> Dummy | None:
"""Read register map."""
logger.debug("args=%s globcfg=%s", args, globcfg) # TODO: remove when arguments are used below
logger.info("Start reading register map ...")
raise NotImplementedError("read_regmap() is not implemented!")


def make_targets(args: Args, globcfg: Dummy, targets: list[Dummy], regmap: Dummy | None) -> None:
"""Make required targets."""
# TODO: remove when arguments are used below
logger.debug("args=%s globcfg=%s targets=%s regmap=%s", args, globcfg, targets, regmap)
logger.info("Start generation ...")
raise NotImplementedError("make_targets() is not implemented!")
101 changes: 101 additions & 0 deletions corsair/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Command-line interface of Corsair."""

from __future__ import annotations

import argparse
from dataclasses import dataclass
from pathlib import Path

from . import utils
from .project import ProjectKind
from .version import __version__


@dataclass
class Args:
"""Application CLI arguments."""

workdir: Path
regmap: Path | None
config: Path | None
targets: list[str]
init_project: ProjectKind | None
debug: bool
no_color: bool


class ArgumentParser(argparse.ArgumentParser):
"""Corsair CLI argument parser."""

def __init__(self) -> None:
"""Parser constructor."""
super().__init__(
prog="corsair",
description="Control and status register (CSR) map generator for HDL projects.",
)

self.add_argument("-v", "--version", action="version", version=f"%(prog)s v{__version__}")
self.add_argument(
"--debug",
action="store_true",
dest="debug",
help="increase logging verbosity level",
)
self.add_argument(
"--no-color",
action="store_true",
dest="no_color",
help="disable use of colors for logging",
)
self.add_argument(
metavar="WORKDIR",
nargs="?",
dest="workdir",
type=Path,
default=Path(),
help="working directory (default is the current directory)",
)
self.add_argument(
"-r",
metavar="PATH",
dest="regmap",
type=Path,
help="register map file",
)
self.add_argument(
"-c",
"--cfg",
metavar="PATH",
type=Path,
dest="config",
help="configuration file",
)
self.add_argument(
"-t",
"--target",
nargs="*",
metavar="NAME",
type=str,
dest="targets",
default=[],
help="make ony selected target(s) from configuration file",
)
self.add_argument(
"-i",
"--init",
metavar="KIND",
type=ProjectKind,
choices=[k.value for k in ProjectKind],
dest="init_project",
help="initialize simple project from template and exit",
)


def parse_args() -> Args:
"""Parse CLI arguments."""
parser = ArgumentParser()

args = parser.parse_args()
args.workdir = utils.resolve_path(args.workdir)

return Args(**vars(args))
Loading

0 comments on commit 3585e44

Please sign in to comment.