-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: rework entry point of the application and add logger (#79)
- Loading branch information
Showing
7 changed files
with
296 additions
and
379 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
Oops, something went wrong.