From 999c0e723240ee783bca17942f77a9d05bbfc168 Mon Sep 17 00:00:00 2001
From: Josh Morman <mormjb@gmail.com>
Date: Thu, 23 Apr 2020 15:03:55 -0400
Subject: utils: add functionality to generate bindings

This currently exists in two places
1) Bindtool (longevity TBD) which calls blocktool to parse the public
header file in the include directory
2) Modtool - binding of headers added to add and bind.  rm, update,
info, etc still TODO
---
 gr-utils/modtool/tools/cppfile_editor.py | 117 +++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)
 create mode 100644 gr-utils/modtool/tools/cppfile_editor.py

(limited to 'gr-utils/modtool/tools/cppfile_editor.py')

diff --git a/gr-utils/modtool/tools/cppfile_editor.py b/gr-utils/modtool/tools/cppfile_editor.py
new file mode 100644
index 0000000000..9f2d3f1fda
--- /dev/null
+++ b/gr-utils/modtool/tools/cppfile_editor.py
@@ -0,0 +1,117 @@
+#
+# Copyright 2013, 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+#
+""" Edit C++ files for bindings """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import re
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class CPPFileEditor(object):
+    """A tool for editing CMakeLists.txt files. """
+    def __init__(self, filename, separator='\n    ', indent='    '):
+        self.filename = filename
+        with open(filename, 'r') as f:
+            self.cfile = f.read()
+        self.separator = separator
+        self.indent = indent
+
+    # def append_value(self, entry, value, to_ignore_start='', to_ignore_end=''):
+    #     """ Add a value to an entry. """
+    #     regexp = re.compile(r'({}\({}[^()]*?)\s*?(\s?{})\)'.format(entry, to_ignore_start, to_ignore_end),
+    #                         re.MULTILINE)
+    #     substi = r'\1' + self.separator + value + r'\2)'
+    #     (self.cfile, nsubs) = regexp.subn(substi, self.cfile, count=1)
+    #     return nsubs
+
+    def append_value(self, start_tag, end_tag, value):
+        """ Add a value to an entry. """
+        cfile_lines = self.cfile.splitlines()
+        start_line_idx = [cfile_lines.index(s) for s in cfile_lines if start_tag in s][0]
+        end_line_idx = [cfile_lines.index(s) for s in cfile_lines if end_tag in s][0]
+
+        self.cfile = '\n'.join((cfile_lines[0:end_line_idx]+[self.indent + value]+cfile_lines[end_line_idx:]))
+        return 1
+
+
+    def remove_value(self, entry, value, to_ignore_start='', to_ignore_end=''):
+        # TODO - gr_modtool rm
+        pass
+
+    def delete_entry(self, entry, value_pattern=''):
+        """Remove an entry from the current buffer."""
+        regexp = r'{}\s*\([^()]*{}[^()]*\)[^\n]*\n'.format(entry, value_pattern)
+        regexp = re.compile(regexp, re.MULTILINE)
+        (self.cfile, nsubs) = re.subn(regexp, '', self.cfile, count=1)
+        return nsubs
+
+    def write(self):
+        """ Write the changes back to the file. """
+        with open(self.filename, 'w') as f:
+            f.write(self.cfile)
+
+    def remove_double_newlines(self):
+        """Simply clear double newlines from the file buffer."""
+        self.cfile = re.compile('\n\n\n+', re.MULTILINE).sub('\n\n', self.cfile)
+
+    def find_filenames_match(self, regex):
+        """ Find the filenames that match a certain regex
+        on lines that aren't comments """
+        filenames = []
+        reg = re.compile(regex)
+        fname_re = re.compile(r'[a-zA-Z]\w+\.\w{1,5}$')
+        for line in self.cfile.splitlines():
+            if len(line.strip()) == 0 or line.strip()[0] == '#':
+                continue
+            for word in re.split('[ /)(\t\n\r\f\v]', line):
+                if fname_re.match(word) and reg.search(word):
+                    filenames.append(word)
+        return filenames
+
+    def disable_file(self, fname):
+        """ Comment out a file.
+        Example:
+        add_library(
+            file1.cc
+        )
+
+        Here, file1.cc becomes #file1.cc with disable_file('file1.cc').
+        """
+        starts_line = False
+        for line in self.cfile.splitlines():
+            if len(line.strip()) == 0 or line.strip()[0] == '#':
+                continue
+            if re.search(r'\b'+fname+r'\b', line):
+                if re.match(fname, line.lstrip()):
+                    starts_line = True
+                break
+        comment_out_re = r'#\1' + '\n' + self.indent
+        if not starts_line:
+            comment_out_re = r'\n' + self.indent + comment_out_re
+        (self.cfile, nsubs) = re.subn(r'(\b'+fname+r'\b)\s*', comment_out_re, self.cfile)
+        if nsubs == 0:
+            logger.warning("Warning: A replacement failed when commenting out {}. Check the CMakeFile.txt manually.".format(fname))
+        elif nsubs > 1:
+            logger.warning("Warning: Replaced {} {} times (instead of once). Check the CMakeFile.txt manually.".format(fname, nsubs))
+
+    def comment_out_lines(self, pattern, comment_str='#'):
+        """ Comments out all lines that match with pattern """
+        for line in self.cfile.splitlines():
+            if re.search(pattern, line):
+                self.cfile = self.cfile.replace(line, comment_str+line)
+
+    def check_for_glob(self, globstr):
+        """ Returns true if a glob as in globstr is found in the cmake file """
+        glob_re = r'GLOB\s[a-z_]+\s"{}"'.format(globstr.replace('*', r'\*'))
+        return re.search(glob_re, self.cfile, flags=re.MULTILINE|re.IGNORECASE) is not None
-- 
cgit v1.2.3


From fbb1f97a4d3e879fcfd15e9826f3b05cfc8504bd Mon Sep 17 00:00:00 2001
From: Josh Morman <mormjb@gmail.com>
Date: Fri, 29 May 2020 08:05:28 -0400
Subject: pybind: incorporate modtool rm

---
 gr-utils/modtool/core/add.py             |  2 +-
 gr-utils/modtool/core/rm.py              | 38 +++++++++++++++++++++++---------
 gr-utils/modtool/tools/cppfile_editor.py | 22 +++++++++++++++---
 3 files changed, 47 insertions(+), 15 deletions(-)

(limited to 'gr-utils/modtool/tools/cppfile_editor.py')

diff --git a/gr-utils/modtool/core/add.py b/gr-utils/modtool/core/add.py
index 872d0a08a9..40bfe74d3f 100644
--- a/gr-utils/modtool/core/add.py
+++ b/gr-utils/modtool/core/add.py
@@ -1,5 +1,5 @@
 #
-# Copyright 2013-2014,2017-2019 Free Software Foundation, Inc.
+# Copyright 2013-2014,2017-2020 Free Software Foundation, Inc.
 #
 # This file is part of GNU Radio
 #
diff --git a/gr-utils/modtool/core/rm.py b/gr-utils/modtool/core/rm.py
index 1a2a3535e4..b65465cd16 100644
--- a/gr-utils/modtool/core/rm.py
+++ b/gr-utils/modtool/core/rm.py
@@ -1,5 +1,5 @@
 #
-# Copyright 2013, 2018, 2019 Free Software Foundation, Inc.
+# Copyright 2013, 2018-2020 Free Software Foundation, Inc.
 #
 # This file is part of GNU Radio
 #
@@ -18,7 +18,7 @@ import sys
 import glob
 import logging
 
-from ..tools import remove_pattern_from_file, CMakeFileEditor
+from ..tools import remove_pattern_from_file, CMakeFileEditor, CPPFileEditor
 from .base import ModTool, ModToolException
 
 logger = logging.getLogger(__name__)
@@ -102,6 +102,19 @@ class ModToolRemove(ModTool):
             for f in py_files_deleted:
                 remove_pattern_from_file(self._file['pyinit'], fr'.*import\s+{f[:-3]}.*')
                 remove_pattern_from_file(self._file['pyinit'], fr'.*from\s+{f[:-3]}\s+import.*\n')
+
+            pb_files_deleted = self._run_subdir('python/bindings', ('*.cc',), ('list',))
+
+            pbdoc_files_deleted = self._run_subdir('python/bindings/docstrings', ('*.h',), ('',))
+
+            # Update python_bindings.cc
+            ed = CPPFileEditor(self._file['ccpybind'])
+            ed.remove_value('// BINDING_FUNCTION_PROTOTYPES(', '// ) END BINDING_FUNCTION_PROTOTYPES', 
+                'void bind_' + self.info['blockname'] + '(py::module& m);')
+            ed.remove_value('// BINDING_FUNCTION_CALLS(', '// ) END BINDING_FUNCTION_CALLS', 
+                'bind_' + self.info['blockname'] + '(m);')
+            ed.write()
+
         if not self.skip_subdirs['grc']:
             self._run_subdir('grc', ('*.yml',), ('install',))
 
@@ -120,7 +133,7 @@ class ModToolRemove(ModTool):
         for g in globs:
             files = files + sorted(glob.glob(f"{path}/{g}"))
         files_filt = []
-        logger.info("Searching for matching files in {path}/:")
+        logger.info(f"Searching for matching files in {path}/:")
         for f in files:
             if re.search(self.info['pattern'], os.path.basename(f)) is not None:
                 files_filt.append(f)
@@ -129,7 +142,6 @@ class ModToolRemove(ModTool):
             return []
         # 2. Delete files, Makefile entries and other occurrences
         files_deleted = []
-        ed = CMakeFileEditor(f'{path}/CMakeLists.txt')
         yes = self.info['yes']
         for f in files_filt:
             b = os.path.basename(f)
@@ -145,11 +157,15 @@ class ModToolRemove(ModTool):
             logger.info(f"Deleting {f}.")
             self.scm.remove_file(f)
             os.unlink(f)
-            logger.info(f"Deleting occurrences of {b} from {path}/CMakeLists.txt...")
-            for var in makefile_vars:
-                ed.remove_value(var, b)
-            if cmakeedit_func is not None:
-                cmakeedit_func(b, ed)
-        ed.write()
-        self.scm.mark_files_updated((f'{path}/CMakeLists.txt'))
+
+            if (os.path.exists(f'{path}/CMakeLists.txt')):
+                ed = CMakeFileEditor(f'{path}/CMakeLists.txt')
+                logger.info(f"Deleting occurrences of {b} from {path}/CMakeLists.txt...")
+                for var in makefile_vars:
+                    ed.remove_value(var, b)
+                if cmakeedit_func is not None:
+                    cmakeedit_func(b, ed)
+                ed.write()
+                self.scm.mark_files_updated((f'{path}/CMakeLists.txt'))
+
         return files_deleted
diff --git a/gr-utils/modtool/tools/cppfile_editor.py b/gr-utils/modtool/tools/cppfile_editor.py
index 9f2d3f1fda..052a887221 100644
--- a/gr-utils/modtool/tools/cppfile_editor.py
+++ b/gr-utils/modtool/tools/cppfile_editor.py
@@ -45,9 +45,25 @@ class CPPFileEditor(object):
         return 1
 
 
-    def remove_value(self, entry, value, to_ignore_start='', to_ignore_end=''):
-        # TODO - gr_modtool rm
-        pass
+    def remove_value(self, start_tag, end_tag, value):
+        
+        cfile_lines = self.cfile.splitlines()
+        try:
+            start_line_idx = [cfile_lines.index(s) for s in cfile_lines if start_tag in s][0]
+            end_line_idx = [cfile_lines.index(s) for s in cfile_lines if end_tag in s][0]
+        except:
+            logger.warning("Could not find start or end tags in search")
+            return 0
+
+        try:            
+            lines_between_tags = cfile_lines[(start_line_idx+1):end_line_idx]
+            remove_index = [lines_between_tags.index(s) for s in cfile_lines if value in s][0]
+            lines_between_tags.pop(remove_index)
+        except:
+            return 0
+
+        self.cfile = '\n'.join((cfile_lines[0:(start_line_idx+1)]+lines_between_tags+cfile_lines[end_line_idx:]))        
+        return 1
 
     def delete_entry(self, entry, value_pattern=''):
         """Remove an entry from the current buffer."""
-- 
cgit v1.2.3