Repository: sigasi/SigasiProjectCreator

Issue:
Extend csv import for include folders and defines
The csv import is currently limited to design files only. We want to extend that:

* If the first column of the csv file contains `include`, interpret the 2nd column of the same line(s) as (an) include path(s)
* If the first column of the csv file contains `define`, interpret the 2nd column of the same line(s) as (a) define(s)

Discussion:
felix-sigasi: Approved.


Patch:
diff --git a/src/SigasiProjectCreator/CsvParser.py b/src/SigasiProjectCreator/CsvParser.py
index 408eeba..83e59fc 100644
--- a/src/SigasiProjectCreator/CsvParser.py
+++ b/src/SigasiProjectCreator/CsvParser.py
@@ -5,7 +5,9 @@
 """
 import csv
 import pathlib
+from collections import defaultdict
 
+from SigasiProjectCreator import abort_if_false, is_valid_name
 from SigasiProjectCreator.ProjectFileParser import ProjectFileParser, project_file_parser, ProjectFileParserResult
 
 
@@ -16,18 +18,21 @@ class CsvParser(ProjectFileParser):
         super().__init__()
 
     def parse_file(self, csv_file, options=None):
-        library_mapping = dict()
+        library_mapping = defaultdict(list)
+        includes = set()
+        defines = []
         with open(csv_file, 'r') as f:
             reader = csv.reader(f, skipinitialspace=True)
             for row in reader:
                 if row:
                     library = row[0].strip()
-                    path = pathlib.Path(row[1].strip()).absolute().resolve()
-                    if path in library_mapping:
-                        if isinstance(library_mapping[path], list):
-                            library_mapping[path].append(library)
-                        else:
-                            library_mapping[path] = [library_mapping[path], library]
+                    if library == '#define':
+                        defines.append(row[1].strip())
                     else:
-                        library_mapping[path] = library
-        return ProjectFileParserResult(library_mapping, None, None)
+                        path = pathlib.Path(row[1].strip()).absolute().resolve()
+                        if library == '#include':
+                            includes.add(path)
+                        else:
+                            abort_if_false(is_valid_name(library), f'Invalid library name: {library}')
+                            library_mapping[path].append(library)
+        return ProjectFileParserResult(library_mapping, verilog_defines=defines, verilog_includes=includes)
diff --git a/src/SigasiProjectCreator/ProjectOptions.py b/src/SigasiProjectCreator/ProjectOptions.py
index 328f30a..7d832d8 100644
--- a/src/SigasiProjectCreator/ProjectOptions.py
+++ b/src/SigasiProjectCreator/ProjectOptions.py
@@ -30,7 +30,8 @@ class ProjectOptions:
         parser.add_argument('--format', action='store', dest='format',
                             choices=ProjectFileParser.project_file_parsers.keys(), default=None,
                             help='Force input format (ignore file extension): ' + ', '.join(
-                                f'{key} ({cls.__doc__})' for key, cls in ProjectFileParser.project_file_parsers.items()))
+                                f'{key} ({cls.__doc__})' for key, cls in
+                                ProjectFileParser.project_file_parsers.items()))
         parser.add_argument('--mapping', action='store', dest='mapping',
                             choices=['file', 'folder'], default='file',
                             help='Library mapping style: `folder` = map folders where possible, `file` = map '
diff --git a/src/SigasiProjectCreator/__init__.py b/src/SigasiProjectCreator/__init__.py
index a70ae1b..bc4ddba 100644
--- a/src/SigasiProjectCreator/__init__.py
+++ b/src/SigasiProjectCreator/__init__.py
@@ -1,3 +1,4 @@
+import re
 import sys
 from os.path import abspath, normcase, splitdrive, join
 from pathlib import PurePath
@@ -23,3 +24,7 @@ def abort_if_false(condition, message, code=5):
 	if not condition:
 		print(message)
 		sys.exit(code)
+
+
+def is_valid_name(text):
+	return re.match('(?!.*__)[a-zA-Z][\\w]*[^_]', text)


Test patch:
diff --git a/tests/CsvParserTest.py b/tests/CsvParserTest.py
new file mode 100644
index 0000000..53f6f38
--- /dev/null
+++ b/tests/CsvParserTest.py
@@ -0,0 +1,57 @@
+import pathlib
+import unittest
+
+from SigasiProjectCreator.CsvParser import CsvParser
+from SigasiProjectCreator.ProjectOptions import ProjectOptions
+
+
+class CsvParserTest(unittest.TestCase):
+    def test_extended_csv(self):
+        input_file = 'tests/test-files/csv/extended.csv'
+        command_line_options = ['the_project', input_file]
+        self.options = ProjectOptions(command_line_options)
+        result = CsvParser().parse_file(input_file, self.options)
+        expected_library_mapping = {
+            pathlib.Path('foo/bar.vhd').absolute().resolve(): ['test1'],
+            pathlib.Path('bahr/define.sv').absolute().resolve(): ['define'],
+            pathlib.Path('clock_generator.vhd').absolute().resolve(): ['test2'],
+            pathlib.Path('dut.vhd').absolute().resolve(): ['test2'],
+            pathlib.Path('testbench.vhd').absolute().resolve(): ['test1'],
+            pathlib.Path('include.vhd').absolute().resolve(): ['include']
+        }
+        expected_includes = {
+            pathlib.Path('includes').absolute().resolve()
+        }
+        expected_defines = ['SIGASI=true']
+        self.assertDictEqual(result.library_mapping, expected_library_mapping, 'Library mapping mismatch')
+        self.assertSetEqual(result.verilog_includes, expected_includes, 'Includes list mismatch')
+        self.assertListEqual(result.verilog_defines, expected_defines, 'Defines list mismatch')
+
+    def test_multimap_csv(self):
+        self.maxDiff = None
+        input_file = 'tests/test-files/csv/multimap.csv'
+        command_line_options = ['the_project', input_file]
+        self.options = ProjectOptions(command_line_options)
+        result = CsvParser().parse_file(input_file, self.options)
+        expected_library_mapping = {
+            pathlib.Path('foo/bar.vhd').absolute().resolve(): ['test1'],
+            pathlib.Path('clock_generator.vhd').absolute().resolve(): ['test2', 'test1', 'test7'],
+            pathlib.Path('dut.vhd').absolute().resolve(): ['test2', 'test3'],
+            pathlib.Path('testbench.vhd').absolute().resolve(): ['test1']
+        }
+        self.assertDictEqual(result.library_mapping, expected_library_mapping, 'Library mapping mismatch')
+        self.assertSetEqual(result.verilog_includes, set(), 'Includes list mismatch')
+        self.assertListEqual(result.verilog_defines, [], 'Defines list mismatch')
+
+    def test_invalid_library_name(self):
+        self.maxDiff = None
+        input_file = 'tests/test-files/csv/invalid_lib.csv'
+        command_line_options = ['the_project', input_file]
+        self.options = ProjectOptions(command_line_options)
+        with self.assertRaises(SystemExit) as context:
+            result = CsvParser().parse_file(input_file, self.options)
+        self.assertEqual('5', str(context.exception))  # exit code 5 from abort_if_false
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/ProjectCreatorTest.py b/tests/ProjectCreatorTest.py
index 0c4e40f..a75e000 100644
--- a/tests/ProjectCreatorTest.py
+++ b/tests/ProjectCreatorTest.py
@@ -435,7 +435,7 @@ class ProjectCreatorTest(unittest.TestCase):
         expected = [
             ['include_folders', 'virtual:/virtual', True, False],
             [pathlib.Path('include_folders/verilog'), '/somelib/verilog', True, True],
-            [pathlib.Path('Common Libraries/uvm'), '/home/wmeeus/git/SigasiProjectCreator_github/uvm/src', True, True],
+            [pathlib.Path('Common Libraries/uvm'), base_path.joinpath('uvm/src').as_posix(), True, True],
             ['Common Libraries', 'virtual:/virtual', True, False],
             ['Common Libraries/IEEE', 'sigasiresource:/vhdl/2008/IEEE', True, False],
             ['Common Libraries/IEEE Synopsys', 'sigasiresource:/vhdl/2008/IEEE%20Synopsys', True, False],
diff --git a/tests/test-files/csv/extended.csv b/tests/test-files/csv/extended.csv
new file mode 100644
index 0000000..498119d
--- /dev/null
+++ b/tests/test-files/csv/extended.csv
@@ -0,0 +1,8 @@
+test1, foo/bar.vhd
+define, bahr/define.sv
+test2, clock_generator.vhd
+#define, SIGASI=true
+test2, dut.vhd
+#include, includes
+test1, testbench.vhd
+include, include.vhd
diff --git a/tests/test-files/csv/invalid_lib.csv b/tests/test-files/csv/invalid_lib.csv
new file mode 100644
index 0000000..ee1b249
--- /dev/null
+++ b/tests/test-files/csv/invalid_lib.csv
@@ -0,0 +1,8 @@
+test1, foo/bar.vhd
+define, bahr/define.sv
+_test2, clock_generator.vhd
+#define, SIGASI=true
+test2, dut.vhd
+#include, includes
+test1, testbench.vhd
+include, include.vhd
diff --git a/tests/test-files/csv/multimap.csv b/tests/test-files/csv/multimap.csv
new file mode 100644
index 0000000..dd99344
--- /dev/null
+++ b/tests/test-files/csv/multimap.csv
@@ -0,0 +1,7 @@
+test1, foo/bar.vhd
+test2, clock_generator.vhd
+test2, dut.vhd
+test1, testbench.vhd
+test1, clock_generator.vhd
+test3, dut.vhd
+test7, clock_generator.vhd