summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gr-utils/CMakeLists.txt1
-rw-r--r--gr-utils/python/modtool/CMakeLists.txt30
-rw-r--r--gr-utils/python/modtool/README.modtool24
-rw-r--r--gr-utils/python/modtool/__init__.py37
-rw-r--r--gr-utils/python/modtool/cli/CMakeLists.txt34
-rw-r--r--gr-utils/python/modtool/cli/__init__.py27
-rw-r--r--gr-utils/python/modtool/cli/add.py133
-rw-r--r--gr-utils/python/modtool/cli/base.py171
-rw-r--r--gr-utils/python/modtool/cli/disable.py52
-rw-r--r--gr-utils/python/modtool/cli/info.py42
-rw-r--r--gr-utils/python/modtool/cli/makeyaml.py57
-rw-r--r--gr-utils/python/modtool/cli/newmod.py72
-rw-r--r--gr-utils/python/modtool/cli/rename.py83
-rw-r--r--gr-utils/python/modtool/cli/rm.py52
-rw-r--r--gr-utils/python/modtool/cli/update.py60
-rw-r--r--gr-utils/python/modtool/core/CMakeLists.txt34
-rw-r--r--gr-utils/python/modtool/core/__init__.py34
-rw-r--r--gr-utils/python/modtool/core/add.py301
-rw-r--r--gr-utils/python/modtool/core/base.py214
-rw-r--r--gr-utils/python/modtool/core/disable.py (renamed from gr-utils/python/modtool/modtool_disable.py)121
-rw-r--r--gr-utils/python/modtool/core/info.py (renamed from gr-utils/python/modtool/modtool_info.py)58
-rw-r--r--gr-utils/python/modtool/core/makeyaml.py (renamed from gr-utils/python/modtool/modtool_makexml.py)124
-rw-r--r--gr-utils/python/modtool/core/newmod.py103
-rw-r--r--gr-utils/python/modtool/core/rename.py (renamed from gr-utils/python/modtool/modtool_rename.py)139
-rw-r--r--gr-utils/python/modtool/core/rm.py (renamed from gr-utils/python/modtool/modtool_rm.py)100
-rw-r--r--gr-utils/python/modtool/core/update.py109
-rw-r--r--gr-utils/python/modtool/grc_xml_generator.py105
-rw-r--r--gr-utils/python/modtool/modtool_add.py367
-rw-r--r--gr-utils/python/modtool/modtool_base.py212
-rw-r--r--gr-utils/python/modtool/modtool_newmod.py104
-rw-r--r--gr-utils/python/modtool/templates/CMakeLists.txt31
-rw-r--r--gr-utils/python/modtool/templates/__init__.py26
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/MANIFEST.md (renamed from gr-utils/python/modtool/gr-newmod/MANIFEST.md)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/apps/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/apps/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindCppUnit.cmake39
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrMiscUtils.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/GrMiscUtils.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPlatform.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPlatform.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPython.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPython.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrSwig.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/GrSwig.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrTest.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/GrTest.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/UseSWIG.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/UseSWIG.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/howtoConfig.cmake (renamed from gr-utils/python/modtool/gr-newmod/cmake/Modules/howtoConfig.cmake)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/cmake/cmake_uninstall.cmake.in (renamed from gr-utils/python/modtool/gr-newmod/cmake/cmake_uninstall.cmake.in)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/docs/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/README.howto (renamed from gr-utils/python/modtool/gr-newmod/docs/README.howto)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.in)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/__init__.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/__init__.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/base.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/base.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/doxyindex.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/doxyindex.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compound.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compound.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/index.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/index.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/text.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/text.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/group_defs.dox (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/other/group_defs.dox)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/main_page.dox (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/other/main_page.dox)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/swig_doc.py (renamed from gr-utils/python/modtool/gr-newmod/docs/doxygen/swig_doc.py)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/examples/README (renamed from gr-utils/python/modtool/gr-newmod/examples/README)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/grc/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/grc/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/include/howto/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/include/howto/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/include/howto/api.h (renamed from gr-utils/python/modtool/gr-newmod/include/howto/api.h)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/lib/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.cc37
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.h39
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/lib/test_howto.cc49
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/python/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/python/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/python/__init__.py (renamed from gr-utils/python/modtool/gr-newmod/python/__init__.py)6
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/swig/CMakeLists.txt (renamed from gr-utils/python/modtool/gr-newmod/swig/CMakeLists.txt)0
-rw-r--r--gr-utils/python/modtool/templates/gr-newmod/swig/howto_swig.i (renamed from gr-utils/python/modtool/gr-newmod/swig/howto_swig.i)2
-rw-r--r--gr-utils/python/modtool/templates/templates.py (renamed from gr-utils/python/modtool/templates.py)106
-rw-r--r--gr-utils/python/modtool/tests/CMakeLists.txt27
-rw-r--r--gr-utils/python/modtool/tests/__init__.py0
-rw-r--r--gr-utils/python/modtool/tests/test_modtool.py294
-rw-r--r--gr-utils/python/modtool/tools/CMakeLists.txt31
-rw-r--r--gr-utils/python/modtool/tools/__init__.py31
-rw-r--r--gr-utils/python/modtool/tools/cmakefile_editor.py (renamed from gr-utils/python/modtool/cmakefile_editor.py)23
-rw-r--r--gr-utils/python/modtool/tools/code_generator.py (renamed from gr-utils/python/modtool/code_generator.py)4
-rw-r--r--gr-utils/python/modtool/tools/grc_yaml_generator.py138
-rw-r--r--gr-utils/python/modtool/tools/parser_cc_block.py (renamed from gr-utils/python/modtool/parser_cc_block.py)37
-rw-r--r--gr-utils/python/modtool/tools/scm.py (renamed from gr-utils/python/modtool/scm.py)6
-rw-r--r--gr-utils/python/modtool/tools/util_functions.py (renamed from gr-utils/python/modtool/util_functions.py)41
-rwxr-xr-xgr-utils/python/utils/gr_modtool33
88 files changed, 2724 insertions, 1276 deletions
diff --git a/gr-utils/CMakeLists.txt b/gr-utils/CMakeLists.txt
index a8dfd91531..2578471b95 100644
--- a/gr-utils/CMakeLists.txt
+++ b/gr-utils/CMakeLists.txt
@@ -51,5 +51,4 @@ if(ENABLE_GR_UTILS)
add_subdirectory(python/utils)
add_subdirectory(python/modtool)
-
endif(ENABLE_GR_UTILS)
diff --git a/gr-utils/python/modtool/CMakeLists.txt b/gr-utils/python/modtool/CMakeLists.txt
index 5fc5b0287f..2cb7c21487 100644
--- a/gr-utils/python/modtool/CMakeLists.txt
+++ b/gr-utils/python/modtool/CMakeLists.txt
@@ -1,4 +1,4 @@
-# Copyright 2011 Free Software Foundation, Inc.
+# Copyright 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -21,28 +21,10 @@ include(GrPython)
GR_PYTHON_INSTALL(FILES
__init__.py
- cmakefile_editor.py
- code_generator.py
- grc_xml_generator.py
- modtool_add.py
- modtool_base.py
- modtool_disable.py
- modtool_info.py
- modtool_makexml.py
- modtool_newmod.py
- modtool_rm.py
- modtool_rename.py
- parser_cc_block.py
- scm.py
- templates.py
- util_functions.py
DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool
)
-set(GR_PKG_MODTOOL_DATA_DIR ${GR_PKG_DATA_DIR}/modtool)
-install(DIRECTORY gr-newmod
- DESTINATION ${GR_PKG_MODTOOL_DATA_DIR}
-)
+set(GR_PKG_MODTOOL_DATA_DIR ${GR_PKG_DATA_DIR}/modtool/templates)
########################################################################
@@ -60,3 +42,11 @@ install(
DESTINATION ${GR_PREFSDIR}
)
+########################################################################
+# Add subdirectories
+########################################################################
+add_subdirectory(core)
+add_subdirectory(cli)
+add_subdirectory(tools)
+add_subdirectory(templates)
+add_subdirectory(tests)
diff --git a/gr-utils/python/modtool/README.modtool b/gr-utils/python/modtool/README.modtool
index 84c2e66c11..bffe49e9e2 100644
--- a/gr-utils/python/modtool/README.modtool
+++ b/gr-utils/python/modtool/README.modtool
@@ -1,14 +1,25 @@
gr_modtool: Swiss Army Knife for editing GNU Radio modules and -components.
-Adding a new subcommand
-=======================
+Adding a new subcommand for Command Line Interface
+==================================================
-* Add a new file called modtool_SUBCOMMAND
+* Add a new file called SUBCOMMAND in the cli directory
+* Create a function cli with the decorator @click.command or @click.group
+* Add the necessary options for the command or command group
+* Add that file to __init__.py and CMakeLists.txt in the cli directory
+
+
+Adding a new subcommand for Exposing as an API
+==============================================
+
+* Add a new file called SUBCOMMAND in the core directory
* Have a look at the other subcommands, it must inherit from ModTool
-* Add that file to __init__.py and CMakeLists.txt
+* Add that file to __init__.py and CMakeLists.txt in the core directory
+
+
+The gr-newmod directory inside the templates directory
+======================================================
-The gr-newmod directory
-=======================
This dir basically contains a copy of gr-howto-write-a-block from the gnuradio
sources, with some differences:
- All example blocks, apps, grc files (...) and references thereto in the
@@ -16,4 +27,3 @@ sources, with some differences:
- In the top-level CMake file, the project is called 'gr-howto'.
- Any time anything relevant is changed in gr-howto-write-a-block, it should
be changed here, too.
-
diff --git a/gr-utils/python/modtool/__init__.py b/gr-utils/python/modtool/__init__.py
index b6d719ac1d..e69de29bb2 100644
--- a/gr-utils/python/modtool/__init__.py
+++ b/gr-utils/python/modtool/__init__.py
@@ -1,37 +0,0 @@
-from __future__ import absolute_import
-from __future__ import unicode_literals
-#
-# Copyright 2013-2014 Free Software Foundation, Inc.
-#
-# This file is part of GNU Radio
-#
-# GNU Radio is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3, or (at your option)
-# any later version.
-#
-# GNU Radio is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with GNU Radio; see the file COPYING. If not, write to
-# the Free Software Foundation, Inc., 51 Franklin Street,
-# Boston, MA 02110-1301, USA.
-#
-
-from .cmakefile_editor import CMakeFileEditor
-from .grc_xml_generator import GRCXMLGenerator
-from .modtool_base import ModTool, ModToolException, get_modtool_modules
-from .modtool_add import ModToolAdd
-from .modtool_disable import ModToolDisable
-from .modtool_info import ModToolInfo
-from .modtool_makexml import ModToolMakeXML
-from .modtool_newmod import ModToolNewModule
-from .modtool_rm import ModToolRemove
-from .modtool_rename import ModToolRename
-from .templates import Templates
-# Leave this at the end
-from .parser_cc_block import ParserCCBlock
-from .util_functions import *
diff --git a/gr-utils/python/modtool/cli/CMakeLists.txt b/gr-utils/python/modtool/cli/CMakeLists.txt
new file mode 100644
index 0000000000..07624a3f68
--- /dev/null
+++ b/gr-utils/python/modtool/cli/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+include(GrPython)
+
+GR_PYTHON_INSTALL(FILES
+ __init__.py
+ add.py
+ base.py
+ disable.py
+ info.py
+ makeyaml.py
+ newmod.py
+ rm.py
+ rename.py
+ update.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/cli
+)
diff --git a/gr-utils/python/modtool/cli/__init__.py b/gr-utils/python/modtool/cli/__init__.py
new file mode 100644
index 0000000000..b6751c322c
--- /dev/null
+++ b/gr-utils/python/modtool/cli/__init__.py
@@ -0,0 +1,27 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from .base import cli, common_params, block_name, ModToolException
+from .base import setup_cli_logger, cli_input
diff --git a/gr-utils/python/modtool/cli/add.py b/gr-utils/python/modtool/cli/add.py
new file mode 100644
index 0000000000..45f749c375
--- /dev/null
+++ b/gr-utils/python/modtool/cli/add.py
@@ -0,0 +1,133 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Module to add new blocks """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import re
+
+import click
+
+from ..core import ModToolAdd
+from ..tools import SequenceCompleter, ask_yes_no
+from .base import common_params, block_name, run, cli_input, ModToolException
+
+
+@click.command('add')
+@click.option('-t', '--block-type', type=click.Choice(ModToolAdd.block_types),
+ help="One of {}.".format(', '.join(ModToolAdd.block_types)))
+@click.option('--license-file',
+ help="File containing the license header for every source code file.")
+@click.option('--copyright',
+ help="Name of the copyright holder (you or your company) MUST be a quoted string.")
+@click.option('--argument-list', default="",
+ help="The argument list for the constructor and make functions.")
+@click.option('--add-python-qa', is_flag=True, default=None,
+ help="If given, Python QA code is automatically added if possible.")
+@click.option('--add-cpp-qa', is_flag=True, default=None,
+ help="If given, C++ QA code is automatically added if possible.")
+@click.option('--skip-cmakefiles', is_flag=True,
+ help="If given, only source files are written, but CMakeLists.txt files are left unchanged.")
+@click.option('-l', '--lang', type=click.Choice(ModToolAdd.language_candidates),
+ help="Programming Language")
+@common_params
+@block_name
+def cli(**kwargs):
+ """Adds a block to the out-of-tree module."""
+ kwargs['cli'] = True
+ self = ModToolAdd(**kwargs)
+ click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green')
+ get_blocktype(self)
+ get_lang(self)
+ click.secho("Language: {}".format({'cpp': 'C++', 'python': 'Python'}[self.info['lang']]), fg='green')
+ if ((self.skip_subdirs['lib'] and self.info['lang'] == 'cpp')
+ or (self.skip_subdirs['python'] and self.info['lang'] == 'python')):
+ raise ModToolException('Missing or skipping relevant subdir.')
+ get_blockname(self)
+ click.secho("Block/code identifier: " + self.info['blockname'], fg='green')
+ self.info['fullblockname'] = self.info['modname'] + '_' + self.info['blockname']
+ if not self.license_file:
+ get_copyrightholder(self)
+ self.info['license'] = self.setup_choose_license()
+ get_arglist(self)
+ get_py_qa(self)
+ get_cpp_qa(self)
+ if self.info['version'] == 'autofoo' and not self.skip_cmakefiles:
+ click.secho("Warning: Autotools modules are not supported. "+
+ "Files will be created, but Makefiles will not be edited.",
+ fg='yellow')
+ self.skip_cmakefiles = True
+ run(self)
+
+def get_blocktype(self):
+ """ Get the blocktype of the block to be added """
+ if self.info['blocktype'] is None:
+ click.secho(str(self.block_types), fg='yellow')
+ with SequenceCompleter(self.block_types):
+ while self.info['blocktype'] not in self.block_types:
+ self.info['blocktype'] = cli_input("Enter block type: ")
+ if self.info['blocktype'] not in self.block_types:
+ click.secho('Must be one of ' + str(self.block_types), fg='yellow')
+
+def get_lang(self):
+ """ Get the Programming Language of the block to be added """
+ if self.info['lang'] is None:
+ with SequenceCompleter(self.language_candidates):
+ while self.info['lang'] not in self.language_candidates:
+ self.info['lang'] = cli_input("Language (python/cpp): ")
+ if self.info['lang'] == 'c++':
+ self.info['lang'] = 'cpp'
+
+def get_blockname(self):
+ """ Get the blockname"""
+ if self.info['blockname'] is None:
+ while not self.info['blockname'] or self.info['blockname'].isspace():
+ self.info['blockname'] = cli_input("Enter name of block/code (without module name prefix): ")
+ if not re.match('[a-zA-Z0-9_]+', self.info['blockname']):
+ raise ModToolException('Invalid block name.')
+
+def get_copyrightholder(self):
+ """ Get the copyrightholder of the block to be added """
+ if self.info['copyrightholder'] is None:
+ self.info['copyrightholder'] = '<+YOU OR YOUR COMPANY+>'
+ elif self.info['is_component']:
+ click.secho("For GNU Radio components the FSF is added as copyright holder",
+ fg='cyan')
+
+def get_arglist(self):
+ """ Get the argument list of the block to be added """
+ if self.info['arglist'] is not None:
+ self.info['arglist'] = cli_input('Enter valid argument list, including default arguments: ')
+
+def get_py_qa(self):
+ """ Get a boolean value for addition of py_qa """
+ if not (self.info['blocktype'] in ('noblock') or self.skip_subdirs['python']):
+ if self.add_py_qa is None:
+ self.add_py_qa = ask_yes_no(click.style('Add Python QA code?', fg='cyan'), True)
+
+def get_cpp_qa(self):
+ """ Get a boolean value for addition of cpp_qa """
+ if self.info['lang'] == 'cpp':
+ if self.add_cc_qa is None:
+ self.add_cc_qa = ask_yes_no(click.style('Add C++ QA code?', fg='cyan'),
+ not self.add_py_qa)
diff --git a/gr-utils/python/modtool/cli/base.py b/gr-utils/python/modtool/cli/base.py
new file mode 100644
index 0000000000..5fa2b8b7b4
--- /dev/null
+++ b/gr-utils/python/modtool/cli/base.py
@@ -0,0 +1,171 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Base CLI module """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import os
+import sys
+import logging
+import functools
+from importlib import import_module
+from pkg_resources import iter_entry_points
+from logging import Formatter, StreamHandler
+
+import click
+from click import ClickException
+from click_plugins import with_plugins
+
+from gnuradio import gr
+
+
+class ModToolException(ClickException):
+ """ Exception class for enhanced CLI interface """
+ def show(self, file = None):
+ """ displays the colored message """
+ click.secho('ModToolException: {}'.format(self.format_message()), fg='red')
+
+
+class CommandCLI(click.Group):
+ """
+ This is a derived class of the implemented click class
+ which overrides some of the functional definitions for external
+ plug-in support
+ """
+ cmd_folder = os.path.abspath(os.path.dirname(__file__))
+
+ def list_commands(self, ctx):
+ """
+ Lists all the commands available in the modtool directory
+ as well as the commands from external plug-ins.
+ """
+ cmds = []
+ for filename in os.listdir(self.cmd_folder):
+ if filename.endswith('.py') and not '_' in filename:
+ cmds.append(filename[:-3])
+ cmds.remove('base')
+ cmds += self.commands
+ return sorted(cmds)
+
+ def get_command(self, ctx, cmd_name):
+ """
+ Returns a command object if it exists. The existing in-tree ModTool
+ command is the priority over the same external plug-in command.
+ """
+ try:
+ if sys.version_info[0] == 2:
+ cmd_name = cmd_name.encode('ascii', 'replace')
+ mod = import_module('gnuradio.modtool.cli.' + cmd_name)
+ except ImportError:
+ return self.commands.get(cmd_name)
+ return mod.cli
+
+
+class ClickHandler(StreamHandler):
+ """
+ This is a derived class of implemented logging class
+ StreamHandler which overrides some of its functional
+ definitions to add colors to the stream output
+ """
+ def emit(self, record):
+ """ Writes message to the stream """
+ colormap = {
+ 'DEBUG': ('white', 'black'),
+ 'INFO': ('blue', None),
+ 'WARNING': ('yellow', None),
+ 'ERROR': ('red', None),
+ 'CRITICAL': ('white', 'red'),
+ }
+ try:
+ msg = self.format(record)
+ colors = colormap.get(record.levelname, (None, None))
+ fgcolor = colors[0]
+ bgcolor = colors[1]
+ click.secho(msg, fg=fgcolor, bg=bgcolor)
+ self.flush()
+ except Exception:
+ self.handleError(record)
+
+
+def setup_cli_logger(logger):
+ """ Sets up logger for CLI parsing """
+ try:
+ import colorama
+ stream_handler = ClickHandler()
+ logger.addHandler(stream_handler)
+ except ImportError:
+ stream_handler = logging.StreamHandler()
+ logger.addHandler(stream_handler)
+ finally:
+ logger.setLevel(logging.INFO)
+
+
+def cli_input(msg):
+ """ Returns enhanced input """
+ return input(click.style(msg, fg='cyan'))
+
+
+def common_params(func):
+ """ Common parameters for various modules"""
+ @click.option('-d', '--directory', default='.',
+ help="Base directory of the module. Defaults to the cwd.")
+ @click.option('--skip-lib', is_flag=True,
+ help="Don't do anything in the lib/ subdirectory.")
+ @click.option('--skip-swig', is_flag=True,
+ help="Don't do anything in the swig/ subdirectory.")
+ @click.option('--skip-python', is_flag=True,
+ help="Don't do anything in the python/ subdirectory.")
+ @click.option('--skip-grc', is_flag=True,
+ help="Don't do anything in the grc/ subdirectory.")
+ @click.option('--scm-mode', type=click.Choice(['yes', 'no', 'auto']),
+ default=gr.prefs().get_string('modtool', 'scm_mode', 'no'),
+ help="Use source control management [ yes | no | auto ]).")
+ @click.option('-y', '--yes', is_flag=True,
+ help="Answer all questions with 'yes'. " +
+ "This can overwrite and delete your files, so be careful.")
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ """ Decorator that wraps common options """
+ return func(*args, **kwargs)
+ return wrapper
+
+
+block_name = click.argument('blockname', nargs=1, required=False, metavar="BLOCK_NAME")
+
+
+@with_plugins(iter_entry_points('gnuradio.modtool.cli.plugins'))
+@click.command(cls=CommandCLI,
+ epilog='Manipulate with GNU Radio modules source code tree. ' +
+ 'Call it without options to run specified command interactively')
+def cli():
+ """A tool for editing GNU Radio out-of-tree modules."""
+ pass
+
+
+def run(module):
+ """Call the run function of the core modules."""
+ try:
+ module.run()
+ except ModToolException as err:
+ click.echo(err, file=sys.stderr)
+ exit(1)
diff --git a/gr-utils/python/modtool/cli/disable.py b/gr-utils/python/modtool/cli/disable.py
new file mode 100644
index 0000000000..4756cda4ee
--- /dev/null
+++ b/gr-utils/python/modtool/cli/disable.py
@@ -0,0 +1,52 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Disable blocks module """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import click
+
+from ..core import get_block_candidates, ModToolDisable
+from ..tools import SequenceCompleter
+from .base import common_params, block_name, run, cli_input
+
+
+@click.command('disable', short_help=ModToolDisable.description)
+@common_params
+@block_name
+def cli(**kwargs):
+ """Disable a block (comments out CMake entries for files)"""
+ kwargs['cli'] = True
+ self = ModToolDisable(**kwargs)
+ click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green')
+ get_pattern(self)
+ run(self)
+
+def get_pattern(self):
+ """ Get the regex pattern for block(s) to be disabled """
+ if self.info['pattern'] is None:
+ block_candidates = get_block_candidates()
+ with SequenceCompleter(block_candidates):
+ self.info['pattern'] = cli_input('Which blocks do you want to disable? (Regex): ')
+ if not self.info['pattern'] or self.info['pattern'].isspace():
+ self.info['pattern'] = '.'
diff --git a/gr-utils/python/modtool/cli/info.py b/gr-utils/python/modtool/cli/info.py
new file mode 100644
index 0000000000..b03902b299
--- /dev/null
+++ b/gr-utils/python/modtool/cli/info.py
@@ -0,0 +1,42 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Returns information about a module """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import click
+
+from ..core import ModToolInfo
+from .base import common_params, run
+
+
+@click.command('info')
+@click.option('--python-readable', is_flag=True,
+ help="Return the output in a format that's easier to read for Python scripts.")
+@click.option('--suggested-dirs',
+ help="Suggest typical include dirs if nothing better can be detected.")
+@common_params
+def cli(**kwargs):
+ """ Return information about a given module """
+ self = ModToolInfo(**kwargs)
+ run(self)
diff --git a/gr-utils/python/modtool/cli/makeyaml.py b/gr-utils/python/modtool/cli/makeyaml.py
new file mode 100644
index 0000000000..621d444958
--- /dev/null
+++ b/gr-utils/python/modtool/cli/makeyaml.py
@@ -0,0 +1,57 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Automatically create YAML bindings for GRC from block code """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import click
+
+from ..core import get_block_candidates, ModToolMakeYAML
+from ..tools import SequenceCompleter
+from .base import common_params, block_name, run, cli_input
+
+
+@click.command('makeyaml', short_help=ModToolMakeYAML.description)
+@common_params
+@block_name
+def cli(**kwargs):
+ """
+ \b
+ Make an YAML file for GRC block bindings
+
+ Note: This does not work on python blocks
+ """
+ kwargs['cli'] = True
+ self = ModToolMakeYAML(**kwargs)
+ click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green')
+ get_pattern(self)
+ run(self)
+
+def get_pattern(self):
+ """ Get the regex pattern for block(s) to be parsed """
+ if self.info['pattern'] is None:
+ block_candidates = get_block_candidates()
+ with SequenceCompleter(block_candidates):
+ self.info['pattern'] = cli_input('Which blocks do you want to parse? (Regex): ')
+ if not self.info['pattern'] or self.info['pattern'].isspace():
+ self.info['pattern'] = '.'
diff --git a/gr-utils/python/modtool/cli/newmod.py b/gr-utils/python/modtool/cli/newmod.py
new file mode 100644
index 0000000000..56fb9e0433
--- /dev/null
+++ b/gr-utils/python/modtool/cli/newmod.py
@@ -0,0 +1,72 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Create a whole new out-of-tree module """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import re
+import os
+
+import click
+
+from gnuradio import gr
+from ..core import ModToolNewModule
+from .base import common_params, run, cli_input, ModToolException
+
+
+@click.command('newmod', short_help=ModToolNewModule.description)
+@click.option('--srcdir',
+ help="Source directory for the module template.")
+@common_params
+@click.argument('module_name', metavar="MODULE-NAME", nargs=1, required=False)
+def cli(**kwargs):
+ """
+ \b
+ Create a new out-of-tree module
+
+ The argument MODULE-NAME is the name of the module to be added.
+ """
+ kwargs['cli'] = True
+ self = ModToolNewModule(**kwargs)
+ get_modname(self)
+ self.dir = os.path.join(self.dir, 'gr-{}'.format(self.info['modname']))
+ try:
+ os.stat(self.dir)
+ except OSError:
+ pass # This is what should happen
+ else:
+ raise ModToolException('The given directory exists.')
+ if self.srcdir is None:
+ self.srcdir = '/usr/local/share/gnuradio/modtool/templates/gr-newmod'
+ self.srcdir = gr.prefs().get_string('modtool', 'newmod_path', self.srcdir)
+ if not os.path.isdir(self.srcdir):
+ raise ModToolException('Could not find gr-newmod source dir.')
+ run(self)
+
+def get_modname(self):
+ """ Get the name of the new module to be added """
+ if self.info['modname'] is None:
+ while not self.info['modname'] or self.info['modname'].isspace():
+ self.info['modname'] = cli_input('Name of the new module: ')
+ if not re.match('[a-zA-Z0-9_]+$', self.info['modname']):
+ raise ModToolException('Invalid module name.')
diff --git a/gr-utils/python/modtool/cli/rename.py b/gr-utils/python/modtool/cli/rename.py
new file mode 100644
index 0000000000..d88c30a75d
--- /dev/null
+++ b/gr-utils/python/modtool/cli/rename.py
@@ -0,0 +1,83 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Module to rename blocks """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import re
+
+import click
+
+from ..core import get_block_candidates, ModToolRename
+from ..tools import SequenceCompleter
+from .base import common_params, block_name, run, cli_input, ModToolException
+
+
+@click.command('rename', short_help=ModToolRename.description)
+@common_params
+@block_name
+@click.argument('new-name', metavar="NEW-BLOCK-NAME", nargs=1, required=False)
+def cli(**kwargs):
+ """
+ \b
+ Rename a block inside a module.
+
+ The argument NEW-BLOCK-NAME is the new name of the block.
+ """
+ kwargs['cli'] = True
+ self = ModToolRename(**kwargs)
+ click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green')
+ # first make sure the old block name is provided
+ get_oldname(self)
+ click.secho("Block/code to rename identifier: " + self.info['oldname'], fg='green')
+ self.info['fulloldname'] = self.info['modname'] + '_' + self.info['oldname']
+ # now get the new block name
+ get_newname(self)
+ click.secho("Block/code identifier: " + self.info['newname'], fg='green')
+ self.info['fullnewname'] = self.info['modname'] + '_' + self.info['newname']
+ run(self)
+
+def get_oldname(self):
+ """ Get the old block name to be replaced """
+ block_candidates = get_block_candidates()
+ if self.info['oldname'] is None:
+ with SequenceCompleter(block_candidates):
+ while not self.info['oldname'] or self.info['oldname'].isspace():
+ self.info['oldname'] = cli_input("Enter name of block/code to rename "+
+ "(without module name prefix): ")
+ if self.info['oldname'] not in block_candidates:
+ choices = [x for x in block_candidates if self.info['oldname'] in x]
+ if len(choices) > 0:
+ click.secho("Suggested alternatives: "+str(choices), fg='yellow')
+ raise ModToolException("Blockname for renaming does not exists!")
+ if not re.match('[a-zA-Z0-9_]+', self.info['oldname']):
+ raise ModToolException('Invalid block name.')
+
+def get_newname(self):
+ """ Get the new block name """
+ if self.info['newname'] is None:
+ while not self.info['newname'] or self.info['newname'].isspace():
+ self.info['newname'] = cli_input("Enter name of block/code "+
+ "(without module name prefix): ")
+ if not re.match('[a-zA-Z0-9_]+', self.info['newname']):
+ raise ModToolException('Invalid block name.')
diff --git a/gr-utils/python/modtool/cli/rm.py b/gr-utils/python/modtool/cli/rm.py
new file mode 100644
index 0000000000..6ba938086d
--- /dev/null
+++ b/gr-utils/python/modtool/cli/rm.py
@@ -0,0 +1,52 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Remove blocks module """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import click
+
+from ..core import get_block_candidates, ModToolRemove
+from ..tools import SequenceCompleter
+from .base import common_params, block_name, run, cli_input
+
+
+@click.command('remove', short_help=ModToolRemove.description)
+@common_params
+@block_name
+def cli(**kwargs):
+ """ Remove block (delete files and remove Makefile entries) """
+ kwargs['cli'] = True
+ self = ModToolRemove(**kwargs)
+ click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green')
+ get_pattern(self)
+ run(self)
+
+def get_pattern(self):
+ """ Returns the regex pattern for block(s) to be removed """
+ if self.info['pattern'] is None:
+ block_candidates = get_block_candidates()
+ with SequenceCompleter(block_candidates):
+ self.info['pattern'] = cli_input('Which blocks do you want to delete? (Regex): ')
+ if not self.info['pattern'] or self.info['pattern'].isspace():
+ self.info['pattern'] = '.'
diff --git a/gr-utils/python/modtool/cli/update.py b/gr-utils/python/modtool/cli/update.py
new file mode 100644
index 0000000000..f5da9077ff
--- /dev/null
+++ b/gr-utils/python/modtool/cli/update.py
@@ -0,0 +1,60 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Module to convert XML bindings to YAML bindings """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import click
+
+from ..core import get_xml_candidates, ModToolUpdate
+from ..tools import SequenceCompleter
+from .base import block_name, run, cli_input, ModToolException
+
+
+@click.command('update', short_help=ModToolUpdate.description)
+@click.option('--complete', is_flag=True, default=None,
+ help="Convert all the XML bindings to YAML.")
+@block_name
+def cli(**kwargs):
+ """ Update the XML bindings to YAML bindings """
+ kwargs['cli'] = True
+ self = ModToolUpdate(**kwargs)
+ click.secho("GNU Radio module name identified: " + self.info['modname'], fg='green')
+ get_blockname(self)
+ run(self)
+
+def get_blockname(self):
+ """ Returns the blockname for block to be updated """
+ if self.info['complete']:
+ return
+ if self.info['blockname'] is None:
+ block_candidates = get_xml_candidates()
+ with SequenceCompleter(block_candidates):
+ self.info['blockname'] = cli_input('Which block do you wish to update? : ')
+ if not self.info['blockname'] or self.info['blockname'].isspace():
+ raise ModToolException('Block name not specified!')
+ if self.info['blockname'] not in block_candidates:
+ choices = [x for x in block_candidates if self.info['blockname'] in x]
+ if len(choices) > 0:
+ click.secho("Suggested alternatives: "+str(choices), fg='yellow')
+ raise ModToolException("The XML bindings does not exists!")
diff --git a/gr-utils/python/modtool/core/CMakeLists.txt b/gr-utils/python/modtool/core/CMakeLists.txt
new file mode 100644
index 0000000000..e05057be19
--- /dev/null
+++ b/gr-utils/python/modtool/core/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright 2011, 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+include(GrPython)
+
+GR_PYTHON_INSTALL(FILES
+ __init__.py
+ add.py
+ base.py
+ disable.py
+ info.py
+ makeyaml.py
+ newmod.py
+ rm.py
+ rename.py
+ update.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/core
+)
diff --git a/gr-utils/python/modtool/core/__init__.py b/gr-utils/python/modtool/core/__init__.py
new file mode 100644
index 0000000000..eb9386cdf6
--- /dev/null
+++ b/gr-utils/python/modtool/core/__init__.py
@@ -0,0 +1,34 @@
+#
+# Copyright 2013-2014, 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from .base import ModTool, ModToolException, get_block_candidates
+from .add import ModToolAdd
+from .disable import ModToolDisable
+from .info import ModToolInfo
+from .makeyaml import ModToolMakeYAML
+from .newmod import ModToolNewModule
+from .rm import ModToolRemove
+from .rename import ModToolRename
+from .update import ModToolUpdate, get_xml_candidates
diff --git a/gr-utils/python/modtool/core/add.py b/gr-utils/python/modtool/core/add.py
new file mode 100644
index 0000000000..c1a5433831
--- /dev/null
+++ b/gr-utils/python/modtool/core/add.py
@@ -0,0 +1,301 @@
+#
+# Copyright 2013-2014,2017,2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Module to add new blocks """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import os
+import re
+import logging
+
+from ..tools import render_template, append_re_line_sequence, CMakeFileEditor
+from ..templates import Templates
+from .base import ModTool, ModToolException
+
+logger = logging.getLogger(__name__)
+
+
+class ModToolAdd(ModTool):
+ """ Add block to the out-of-tree module. """
+ name = 'add'
+ description = 'Add new block into a module.'
+ block_types = ('sink', 'source', 'sync', 'decimator', 'interpolator',
+ 'general', 'tagged_stream', 'hier', 'noblock')
+ language_candidates = ('cpp', 'python', 'c++')
+
+ def __init__(self, blockname=None, block_type=None, lang=None, copyright=None,
+ license_file=None, argument_list="", add_python_qa=False,
+ add_cpp_qa=False, skip_cmakefiles=False, **kwargs):
+ ModTool.__init__(self, blockname, **kwargs)
+ self.info['blocktype'] = block_type
+ self.info['lang'] = lang
+ self.license_file = license_file
+ self.info['copyrightholder'] = copyright
+ self.info['arglist'] = argument_list
+ self.add_py_qa = add_python_qa
+ self.add_cc_qa = add_cpp_qa
+ self.skip_cmakefiles = skip_cmakefiles
+
+ def validate(self):
+ """ Validates the arguments """
+ ModTool._validate(self)
+ if self.info['blocktype'] is None:
+ raise ModToolException('Blocktype not specified.')
+ if self.info['blocktype'] not in self.block_types:
+ raise ModToolException('Invalid blocktype')
+ if self.info['lang'] is None:
+ raise ModToolException('Programming language not specified.')
+ if self.info['lang'] not in self.language_candidates:
+ raise ModToolException('Invalid programming language.')
+ if self.info['blockname'] is None:
+ raise ModToolException('Blockname not specified.')
+ if not re.match('[a-zA-Z0-9_]+', self.info['blockname']):
+ raise ModToolException('Invalid block name.')
+ if not isinstance(self.add_py_qa, bool):
+ raise ModToolException('Expected a boolean value for add_python_qa.')
+ if not isinstance(self.add_cc_qa, bool):
+ raise ModToolException('Expected a boolean value for add_cpp_qa.')
+ if not isinstance(self.skip_cmakefiles, bool):
+ raise ModToolException('Expected a boolean value for skip_cmakefiles.')
+
+ def assign(self):
+ if self.info['lang'] == 'c++':
+ self.info['lang'] = 'cpp'
+ if ((self.skip_subdirs['lib'] and self.info['lang'] == 'cpp')
+ or (self.skip_subdirs['python'] and self.info['lang'] == 'python')):
+ raise ModToolException('Missing or skipping relevant subdir.')
+ self.info['fullblockname'] = self.info['modname'] + '_' + self.info['blockname']
+ if not self.license_file:
+ if self.info['copyrightholder'] is None:
+ self.info['copyrightholder'] = '<+YOU OR YOUR COMPANY+>'
+ self.info['license'] = self.setup_choose_license()
+ if (self.info['blocktype'] in ('noblock') or self.skip_subdirs['python']):
+ self.add_py_qa = False
+ if not self.info['lang'] == 'cpp':
+ self.add_cc_qa = False
+ if self.info['version'] == 'autofoo' and not self.skip_cmakefiles:
+ self.skip_cmakefiles = True
+
+ def setup_choose_license(self):
+ """ Select a license by the following rules, in this order:
+ 1) The contents of the file given by --license-file
+ 2) The contents of the file LICENSE or LICENCE in the modules
+ top directory
+ 3) The default license. """
+ if self.license_file is not None \
+ and os.path.isfile(self.license_file):
+ with open(self.license_file) as f:
+ return f.read()
+ elif os.path.isfile('LICENSE'):
+ with open('LICENSE') as f:
+ return f.read()
+ elif os.path.isfile('LICENCE'):
+ with open('LICENCE') as f:
+ return f.read()
+ elif self.info['is_component']:
+ return Templates['grlicense']
+ else:
+ return Templates['defaultlicense'].format(**self.info)
+
+ def _write_tpl(self, tpl, path, fname):
+ """ Shorthand for writing a substituted template to a file"""
+ path_to_file = os.path.join(path, fname)
+ logger.info("Adding file '{}'...".format(path_to_file))
+ with open(path_to_file, 'w') as f:
+ f.write(render_template(tpl, **self.info))
+ self.scm.add_files((path_to_file,))
+
+ def run(self):
+ """ Go, go, go. """
+ # This portion will be covered by the CLI
+ if not self.cli:
+ self.validate()
+ self.assign()
+
+ has_swig = (
+ self.info['lang'] == 'cpp'
+ and not self.skip_subdirs['swig']
+ )
+ has_grc = False
+ if self.info['lang'] == 'cpp':
+ self._run_lib()
+ has_grc = has_swig
+ else: # Python
+ self._run_python()
+ if self.info['blocktype'] != 'noblock':
+ has_grc = True
+ if has_swig:
+ self._run_swig()
+ if self.add_py_qa:
+ self._run_python_qa()
+ if has_grc and not self.skip_subdirs['grc']:
+ self._run_grc()
+
+ def _run_cc_qa(self):
+ " Add C++ QA files for 3.7 API if intructed from _run_lib"
+ fname_qa_h = 'qa_{}.h'.format(self.info['blockname'])
+ fname_qa_cc = 'qa_{}.cc'.format(self.info['blockname'])
+ self._write_tpl('qa_cpp', 'lib', fname_qa_cc)
+ self._write_tpl('qa_h', 'lib', fname_qa_h)
+ if self.skip_cmakefiles:
+ return
+ try:
+ append_re_line_sequence(self._file['cmlib'],
+ '\$\{CMAKE_CURRENT_SOURCE_DIR\}/qa_%s.cc.*\n' % self.info['modname'],
+ ' ${CMAKE_CURRENT_SOURCE_DIR}/qa_%s.cc' % self.info['blockname'])
+ append_re_line_sequence(self._file['qalib'],
+ '#include.*\n',
+ '#include "{}"'.format(fname_qa_h))
+ append_re_line_sequence(self._file['qalib'],
+ '(addTest.*suite.*\n|new CppUnit.*TestSuite.*\n)',
+ ' s->addTest(gr::{}::qa_{}::suite());'.format(self.info['modname'],
+ self.info['blockname'])
+ )
+ self.scm.mark_files_updated((self._file['qalib'],))
+ except IOError:
+ logger.warning("Can't add C++ QA files.")
+
+ def _run_lib(self):
+ """ Do everything that needs doing in the subdir 'lib' and 'include'.
+ - add .cc and .h files
+ - include them into CMakeLists.txt
+ - check if C++ QA code is req'd
+ - if yes, create qa_*.{cc,h} and add them to CMakeLists.txt
+ """
+ fname_cc = None
+ fname_h = None
+ if self.info['version'] in ('37', '38'):
+ fname_h = self.info['blockname'] + '.h'
+ fname_cc = self.info['blockname'] + '.cc'
+ if self.info['blocktype'] in ('source', 'sink', 'sync', 'decimator',
+ 'interpolator', 'general', 'hier', 'tagged_stream'):
+ fname_cc = self.info['blockname'] + '_impl.cc'
+ self._write_tpl('block_impl_h', 'lib', self.info['blockname'] + '_impl.h')
+ self._write_tpl('block_impl_cpp', 'lib', fname_cc)
+ self._write_tpl('block_def_h', self.info['includedir'], fname_h)
+ else: # Pre-3.7 or autotools
+ fname_h = self.info['fullblockname'] + '.h'
+ fname_cc = self.info['fullblockname'] + '.cc'
+ self._write_tpl('block_h36', self.info['includedir'], fname_h)
+ self._write_tpl('block_cpp36', 'lib', fname_cc)
+ if self.add_cc_qa:
+ if self.info['version'] == '37':
+ self._run_cc_qa()
+ elif self.info['version'] == '36':
+ logger.warning("Warning: C++ QA files not supported for 3.6-style OOTs.")
+ elif self.info['version'] == 'autofoo':
+ logger.warning("Warning: C++ QA files not supported for autotools.")
+ if not self.skip_cmakefiles:
+ ed = CMakeFileEditor(self._file['cmlib'])
+ cmake_list_var = '[a-z]*_?' + self.info['modname'] + '_sources'
+ if not ed.append_value('list', fname_cc, to_ignore_start='APPEND ' + cmake_list_var):
+ ed.append_value('add_library', fname_cc)
+ ed.write()
+ ed = CMakeFileEditor(self._file['cminclude'])
+ ed.append_value('install', fname_h, to_ignore_end='DESTINATION[^()]+')
+ ed.write()
+ self.scm.mark_files_updated((self._file['cminclude'], self._file['cmlib']))
+
+ def _run_swig(self):
+ """ Do everything that needs doing in the subdir 'swig'.
+ - Edit main *.i file
+ """
+ if self._get_mainswigfile() is None:
+ logger.warning('Warning: No main swig file found.')
+ return
+ logger.info("Editing {}...".format(self._file['swig']))
+ mod_block_sep = '/'
+ if self.info['version'] == '36':
+ mod_block_sep = '_'
+ swig_block_magic_str = render_template('swig_block_magic', **self.info)
+ with open(self._file['swig'], 'a') as f:
+ f.write(swig_block_magic_str)
+ include_str = '#include "{}{}{}.h"'.format(
+ {True: 'gnuradio/' + self.info['modname'], False: self.info['modname']}[self.info['is_component']],
+ mod_block_sep,
+ self.info['blockname'])
+ with open(self._file['swig'], 'r') as f:
+ oldfile = f.read()
+ if re.search('#include', oldfile):
+ append_re_line_sequence(self._file['swig'], '^#include.*\n', include_str)
+ else: # I.e., if the swig file is empty
+ regexp = re.compile('^%\{\n', re.MULTILINE)
+ oldfile = regexp.sub('%%{\n%s\n' % include_str, oldfile, count=1)
+ with open(self._file['swig'], 'w') as f:
+ f.write(oldfile)
+ self.scm.mark_files_updated((self._file['swig'],))
+
+ def _run_python_qa(self):
+ """ Do everything that needs doing in the subdir 'python' to add
+ QA code.
+ - add .py files
+ - include in CMakeLists.txt
+ """
+ fname_py_qa = 'qa_' + self.info['blockname'] + '.py'
+ self._write_tpl('qa_python', self.info['pydir'], fname_py_qa)
+ os.chmod(os.path.join(self.info['pydir'], fname_py_qa), 0o755)
+ self.scm.mark_files_updated((os.path.join(self.info['pydir'], fname_py_qa),))
+ if self.skip_cmakefiles or CMakeFileEditor(self._file['cmpython']).check_for_glob('qa_*.py'):
+ return
+ logger.info("Editing {}/CMakeLists.txt...".format(self.info['pydir']))
+ with open(self._file['cmpython'], 'a') as f:
+ f.write(
+ 'GR_ADD_TEST(qa_%s ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/%s)\n' % \
+ (self.info['blockname'], fname_py_qa))
+ self.scm.mark_files_updated((self._file['cmpython'],))
+
+ def _run_python(self):
+ """ Do everything that needs doing in the subdir 'python' to add
+ a Python block.
+ - add .py file
+ - include in CMakeLists.txt
+ - include in __init__.py
+ """
+ fname_py = self.info['blockname'] + '.py'
+ self._write_tpl('block_python', self.info['pydir'], fname_py)
+ append_re_line_sequence(self._file['pyinit'],
+ '(^from.*import.*\n|# import any pure.*\n)',
+ 'from .{} import {}'.format(self.info['blockname'], self.info['blockname']))
+ self.scm.mark_files_updated((self._file['pyinit'],))
+ if self.skip_cmakefiles:
+ return
+ ed = CMakeFileEditor(self._file['cmpython'])
+ ed.append_value('GR_PYTHON_INSTALL', fname_py, to_ignore_end='DESTINATION[^()]+')
+ ed.write()
+ self.scm.mark_files_updated((self._file['cmpython'],))
+
+ def _run_grc(self):
+ """ Do everything that needs doing in the subdir 'grc' to add
+ a GRC bindings YAML file.
+ - add .yml file
+ - include in CMakeLists.txt
+ """
+ fname_grc = self.info['fullblockname'] + '.block.yml'
+ self._write_tpl('grc_yml', 'grc', fname_grc)
+ ed = CMakeFileEditor(self._file['cmgrc'], '\n ')
+ if self.skip_cmakefiles or ed.check_for_glob('*.yml'):
+ return
+ logger.info("Editing grc/CMakeLists.txt...")
+ ed.append_value('install', fname_grc, to_ignore_end='DESTINATION[^()]+')
+ ed.write()
+ self.scm.mark_files_updated((self._file['cmgrc'],))
diff --git a/gr-utils/python/modtool/core/base.py b/gr-utils/python/modtool/core/base.py
new file mode 100644
index 0000000000..5d133892e6
--- /dev/null
+++ b/gr-utils/python/modtool/core/base.py
@@ -0,0 +1,214 @@
+#
+# Copyright 2013, 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Base class for the modules """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import os
+import re
+import glob
+import logging
+import itertools
+from types import SimpleNamespace
+
+from gnuradio import gr
+from ..tools import get_modname, SCMRepoFactory
+from ..cli import setup_cli_logger
+
+logger = logging.getLogger('gnuradio.modtool')
+
+
+def get_block_candidates():
+ """ Returns a list of all possible blocknames """
+ block_candidates = []
+ cpp_filters = ["*.cc", "*.cpp"]
+ cpp_blocks = []
+ for ftr in cpp_filters:
+ cpp_blocks += [x for x in glob.glob1("lib", ftr) if not (x.startswith('qa_') or
+ x.startswith('test_'))]
+ python_blocks = [x for x in glob.glob1("python", "*.py") if not (x.startswith('qa_') or
+ x.startswith('build') or x.startswith('__init__'))]
+ for block in itertools.chain(cpp_blocks, python_blocks):
+ block = os.path.splitext(block)[0]
+ block = block.split('_impl')[0]
+ block_candidates.append(block)
+ return block_candidates
+
+
+class ModToolException(Exception):
+ """ Standard exception for modtool classes. """
+ pass
+
+
+class ModTool(object):
+ """ Base class for all modtool command classes. """
+ name = 'base'
+ description = None
+
+ def __init__(self, blockname=None, module_name=None, **kwargs):
+ # List subdirs where stuff happens
+ self._subdirs = ['lib', 'include', 'python', 'swig', 'grc']
+ self.has_subdirs = {}
+ self.skip_subdirs = {}
+ self.info = {}
+ self._file = {}
+ for subdir in self._subdirs:
+ self.has_subdirs[subdir] = False
+ self.skip_subdirs[subdir] = False
+ self.info['blockname'] = blockname
+ self.info['modname'] = module_name
+ self.cli = kwargs.get('cli', False)
+ self.dir = kwargs.get('directory', '.')
+ self.skip_subdirs['lib'] = kwargs.get('skip_lib', False)
+ self.skip_subdirs['python'] = kwargs.get('skip_python', False)
+ self.skip_subdirs['swig'] = kwargs.get('skip_swig', False)
+ self.skip_subdirs['grc'] = kwargs.get('skip_grc', False)
+ self._scm = kwargs.get('scm_mode',
+ gr.prefs().get_string('modtool', 'scm_mode', 'no'))
+ if not self.cli:
+ logging.basicConfig(level=logging.ERROR, format='%(message)s')
+ self.info['yes'] = True
+ else:
+ self.info['yes'] = kwargs.get('yes', False)
+ setup_cli_logger(logger)
+
+ if not type(self).__name__ in ['ModToolInfo', 'ModToolNewModule']:
+ if self.cli:
+ self._validate()
+
+ def _validate(self):
+ """ Validates the arguments """
+ if not isinstance(self.skip_subdirs['lib'], bool):
+ raise ModToolException('Expected a boolean value for skip_lib')
+ if not isinstance(self.skip_subdirs['swig'], bool):
+ raise ModToolException('Expected a boolean value for skip_swig')
+ if not isinstance(self.skip_subdirs['python'], bool):
+ raise ModToolException('Expected a boolean value for skip_python')
+ if not isinstance(self.skip_subdirs['grc'], bool):
+ raise ModToolException('Expected a boolean value for skip_grc')
+ self._assign()
+
+ def _assign(self):
+ if not self._check_directory(self.dir):
+ raise ModToolException('No GNU Radio module found in the given directory.')
+ if self.info['modname'] is None:
+ self.info['modname'] = get_modname()
+ if self.info['modname'] is None:
+ raise ModToolException('No GNU Radio module found in the given directory.')
+ if self.info['version'] == '36' and (
+ os.path.isdir(os.path.join('include', self.info['modname'])) or
+ os.path.isdir(os.path.join('include', 'gnuradio', self.info['modname']))
+ ):
+ self.info['version'] = '37'
+ if not os.path.isfile(os.path.join('cmake', 'Modules', 'FindCppUnit.cmake')):
+ self.info['version'] = '38'
+ if self.skip_subdirs['lib'] or not self.has_subdirs['lib']:
+ self.skip_subdirs['lib'] = True
+ if not self.has_subdirs['python']:
+ self.skip_subdirs['python'] = True
+ if self._get_mainswigfile() is None or not self.has_subdirs['swig']:
+ self.skip_subdirs['swig'] = True
+ if not self.has_subdirs['grc']:
+ self.skip_subdirs['grc'] = True
+
+ self._setup_files()
+ self._setup_scm()
+
+ def _setup_files(self):
+ """ Initialise the self._file[] dictionary """
+ if not self.skip_subdirs['swig']:
+ self._file['swig'] = os.path.join('swig', self._get_mainswigfile())
+ self.info['pydir'] = 'python'
+ if os.path.isdir(os.path.join('python', self.info['modname'])):
+ self.info['pydir'] = os.path.join('python', self.info['modname'])
+ self._file['qalib'] = os.path.join('lib', 'qa_{}.cc'.format(self.info['modname']))
+ self._file['pyinit'] = os.path.join(self.info['pydir'], '__init__.py')
+ self._file['cmlib'] = os.path.join('lib', 'CMakeLists.txt')
+ self._file['cmgrc'] = os.path.join('grc', 'CMakeLists.txt')
+ self._file['cmpython'] = os.path.join(self.info['pydir'], 'CMakeLists.txt')
+ if self.info['is_component']:
+ self.info['includedir'] = os.path.join('include', 'gnuradio', self.info['modname'])
+ elif self.info['version'] in ('37', '38'):
+ self.info['includedir'] = os.path.join('include', self.info['modname'])
+ else:
+ self.info['includedir'] = 'include'
+ self._file['cminclude'] = os.path.join(self.info['includedir'], 'CMakeLists.txt')
+ self._file['cmswig'] = os.path.join('swig', 'CMakeLists.txt')
+ self._file['cmfind'] = os.path.join('cmake', 'Modules', 'howtoConfig.cmake')
+
+
+ def _setup_scm(self, mode='active'):
+ """ Initialize source control management. """
+ self.options = SimpleNamespace(scm_mode = self._scm)
+ if mode == 'active':
+ self.scm = SCMRepoFactory(self.options, '.').make_active_scm_manager()
+ else:
+ self.scm = SCMRepoFactory(self.options, '.').make_empty_scm_manager()
+ if self.scm is None:
+ logger.error("Error: Can't set up SCM.")
+ exit(1)
+
+ def _check_directory(self, directory):
+ """ Guesses if dir is a valid GNU Radio module directory by looking for
+ CMakeLists.txt and at least one of the subdirs lib/, python/ and swig/.
+ Changes the directory, if valid. """
+ has_makefile = False
+ try:
+ files = os.listdir(directory)
+ os.chdir(directory)
+ except OSError:
+ logger.error("Can't read or chdir to directory {}.".format(directory))
+ return False
+ self.info['is_component'] = False
+ for f in files:
+ if os.path.isfile(f) and f == 'CMakeLists.txt':
+ with open(f) as filetext:
+ if re.search('find_package\(Gnuradio', filetext.read()) is not None:
+ self.info['version'] = '36' # Might be 37, check that later
+ has_makefile = True
+ elif re.search('GR_REGISTER_COMPONENT', filetext.read()) is not None:
+ self.info['version'] = '36' # Might be 37, check that later
+ self.info['is_component'] = True
+ has_makefile = True
+ # TODO search for autofoo
+ elif os.path.isdir(f):
+ if (f in list(self.has_subdirs.keys())):
+ self.has_subdirs[f] = True
+ else:
+ self.skip_subdirs[f] = True
+ return bool(has_makefile and (list(self.has_subdirs.values())))
+
+ def _get_mainswigfile(self):
+ """ Find out which name the main SWIG file has. In particular, is it
+ a MODNAME.i or a MODNAME_swig.i? Returns None if none is found. """
+ modname = self.info['modname']
+ swig_files = (modname + '.i',
+ modname + '_swig.i')
+ for fname in swig_files:
+ if os.path.isfile(os.path.join(self.dir, 'swig', fname)):
+ return fname
+ return None
+
+ def run(self):
+ """ Override this. """
+ raise NotImplementedError('Module implementation missing')
diff --git a/gr-utils/python/modtool/modtool_disable.py b/gr-utils/python/modtool/core/disable.py
index ed55389fb7..ad1f78ef91 100644
--- a/gr-utils/python/modtool/modtool_disable.py
+++ b/gr-utils/python/modtool/core/disable.py
@@ -1,5 +1,5 @@
#
-# Copyright 2013 Free Software Foundation, Inc.
+# Copyright 2013, 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -27,9 +27,13 @@ from __future__ import unicode_literals
import os
import re
import sys
+import logging
-from .modtool_base import ModTool
-from .cmakefile_editor import CMakeFileEditor
+from ..cli import cli_input
+from ..tools import CMakeFileEditor
+from .base import ModTool, ModToolException
+
+logger = logging.getLogger(__name__)
class ModToolDisable(ModTool):
@@ -37,24 +41,17 @@ class ModToolDisable(ModTool):
name = 'disable'
description = 'Disable selected block in module.'
- def __init__(self):
- ModTool.__init__(self)
-
- @staticmethod
- def setup_parser(parser):
- ModTool.setup_parser_block(parser)
+ def __init__(self, blockname=None, **kwargs):
+ ModTool.__init__(self, blockname, **kwargs)
+ self.info['pattern'] = blockname
- def setup(self, options):
- ModTool.setup(self, options)
+ def validate(self):
+ """ Validates the arguments """
+ ModTool._validate(self)
+ if not self.info['pattern'] or self.info['pattern'].isspace():
+ raise ModToolException("Invalid pattern!")
- if options.blockname is not None:
- self._info['pattern'] = options.blockname
- else:
- self._info['pattern'] = input('Which blocks do you want to disable? (Regex): ')
- if len(self._info['pattern']) == 0:
- self._info['pattern'] = '.'
-
- def run(self, options):
+ def run(self):
""" Go, go, go! """
def _handle_py_qa(cmake, fname):
""" Do stuff for py qa """
@@ -64,29 +61,31 @@ class ModToolDisable(ModTool):
def _handle_py_mod(cmake, fname):
""" Do stuff for py extra files """
try:
- initfile = open(self._file['pyinit']).read()
+ with open(self._file['pyinit']) as f:
+ initfile = f.read()
except IOError:
- print("Could not edit __init__.py, that might be a problem.")
+ logger.warning("Could not edit __init__.py, that might be a problem.")
return False
pymodname = os.path.splitext(fname)[0]
initfile = re.sub(r'((from|import)\s+\b'+pymodname+r'\b)', r'#\1', initfile)
- open(self._file['pyinit'], 'w').write(initfile)
+ with open(self._file['pyinit'], 'w') as f:
+ f.write(initfile)
self.scm.mark_file_updated(self._file['pyinit'])
return False
def _handle_cc_qa(cmake, fname):
""" Do stuff for cc qa """
- if self._info['version'] == '37':
+ if self.info['version'] == '37':
cmake.comment_out_lines(r'\$\{CMAKE_CURRENT_SOURCE_DIR\}/'+fname)
fname_base = os.path.splitext(fname)[0]
ed = CMakeFileEditor(self._file['qalib']) # Abusing the CMakeFileEditor...
- ed.comment_out_lines(r'#include\s+"%s.h"' % fname_base, comment_str='//')
- ed.comment_out_lines(r'%s::suite\(\)' % fname_base, comment_str='//')
+ ed.comment_out_lines(r'#include\s+"{}.h"'.format(fname_base), comment_str='//')
+ ed.comment_out_lines(r'{}::suite\(\)'.format(fname_base), comment_str='//')
ed.write()
self.scm.mark_file_updated(self._file['qalib'])
- elif self._info['version'] == '38':
+ elif self.info['version'] == '38':
fname_qa_cc = 'qa_{}.cc'.format(self._info['blockname'])
cmake.comment_out_lines(fname_qa_cc)
- elif self._info['version'] == '36':
+ elif self.info['version'] == '36':
cmake.comment_out_lines('add_executable.*'+fname)
cmake.comment_out_lines('target_link_libraries.*'+os.path.splitext(fname)[0])
cmake.comment_out_lines('GR_ADD_TEST.*'+os.path.splitext(fname)[0])
@@ -95,60 +94,68 @@ class ModToolDisable(ModTool):
def _handle_h_swig(cmake, fname):
""" Comment out include files from the SWIG file,
as well as the block magic """
- swigfile = open(self._file['swig']).read()
- (swigfile, nsubs) = re.subn(r'(.include\s+"(%s/)?%s")' % (
- self._info['modname'], fname), r'//\1', swigfile)
+ with open(self._file['swig']) as f:
+ swigfile = f.read()
+ (swigfile, nsubs) = re.subn('(.include\s+"({}/)?{}")'.format(
+ self.info['modname'], fname),
+ r'//\1', swigfile)
if nsubs > 0:
- print("Changing %s..." % self._file['swig'])
+ logger.info("Changing {}...".format(self._file['swig']))
if nsubs > 1: # Need to find a single BLOCK_MAGIC
- blockname = os.path.splitext(fname[len(self._info['modname'])+1:])[0]
- if self._info['version'] in ('37', '38'):
+ blockname = os.path.splitext(fname[len(self.info['modname'])+1:])[0]
+ if self.info['version'] in ('37', '38'):
blockname = os.path.splitext(fname)[0]
- (swigfile, nsubs) = re.subn('(GR_SWIG_BLOCK_MAGIC2?.+%s.+;)' % blockname, r'//\1', swigfile)
+ (swigfile, nsubs) = re.subn('(GR_SWIG_BLOCK_MAGIC2?.+{}.+;)'.format(blockname), r'//\1', swigfile)
if nsubs > 1:
- print("Hm, changed more then expected while editing %s." % self._file['swig'])
- open(self._file['swig'], 'w').write(swigfile)
+ logger.warning("Hm, changed more then expected while editing {}.".format(self._file['swig']))
+ with open(self._file['swig'], 'w') as f:
+ f.write(swigfile)
self.scm.mark_file_updated(self._file['swig'])
return False
def _handle_i_swig(cmake, fname):
""" Comment out include files from the SWIG file,
as well as the block magic """
- swigfile = open(self._file['swig']).read()
- blockname = os.path.splitext(fname[len(self._info['modname'])+1:])[0]
- if self._info['version'] in ('37', '38'):
+ with open(self._file['swig']) as f:
+ swigfile = f.read()
+ blockname = os.path.splitext(fname[len(self.info['modname'])+1:])[0]
+ if self.info['version'] in ('37', '38'):
blockname = os.path.splitext(fname)[0]
- swigfile = re.sub(r'(%include\s+"'+fname+r'")', r'//\1', swigfile)
- print("Changing %s..." % self._file['swig'])
+ swigfile = re.sub(r'(%include\s+"'+fname+'")', r'//\1', swigfile)
+ logger.info("Changing {}...".format(self._file['swig']))
swigfile = re.sub('(GR_SWIG_BLOCK_MAGIC2?.+'+blockname+'.+;)', r'//\1', swigfile)
- open(self._file['swig'], 'w').write(swigfile)
+ with open(self._file['swig'], 'w') as f:
+ f.write(swigfile)
self.scm.mark_file_updated(self._file['swig'])
return False
- self.setup(options)
+
+ # This portion will be covered by the CLI
+ if not self.cli:
+ self.validate()
# List of special rules: 0: subdir, 1: filename re match, 2: callback
special_treatments = (
- ('python', r'qa.+py$', _handle_py_qa),
- ('python', r'^(?!qa).+py$', _handle_py_mod),
- ('lib', r'qa.+\.cc$', _handle_cc_qa),
- ('include/%s' % self._info['modname'], r'.+\.h$', _handle_h_swig),
- ('include', r'.+\.h$', _handle_h_swig),
- ('swig', r'.+\.i$', _handle_i_swig)
+ ('python', r'qa.+py$', _handle_py_qa),
+ ('python', r'^(?!qa).+py$', _handle_py_mod),
+ ('lib', r'qa.+\.cc$', _handle_cc_qa),
+ ('include/{}'.format(self.info['modname']), r'.+\.h$', _handle_h_swig),
+ ('include', r'.+\.h$', _handle_h_swig),
+ ('swig', r'.+\.i$', _handle_i_swig)
)
for subdir in self._subdirs:
- if self._skip_subdirs[subdir]:
+ if self.skip_subdirs[subdir]:
continue
- if self._info['version'] in ('37', '38') and subdir == 'include':
- subdir = 'include/%s' % self._info['modname']
+ if self.info['version'] in ('37', '38') and subdir == 'include':
+ subdir = 'include/{}'.format(self.info['modname'])
try:
cmake = CMakeFileEditor(os.path.join(subdir, 'CMakeLists.txt'))
except IOError:
continue
- print("Traversing %s..." % subdir)
- filenames = cmake.find_filenames_match(self._info['pattern'])
- yes = self._info['yes']
+ logger.info("Traversing {}...".format(subdir))
+ filenames = cmake.find_filenames_match(self.info['pattern'])
+ yes = self.info['yes']
for fname in filenames:
file_disabled = False
if not yes:
- ans = input("Really disable %s? [Y/n/a/q]: " % fname).lower().strip()
+ ans = cli_input("Really disable {}? [Y/n/a/q]: ".format(fname)).lower().strip()
if ans == 'a':
yes = True
if ans == 'q':
@@ -162,4 +169,4 @@ class ModToolDisable(ModTool):
cmake.disable_file(fname)
cmake.write()
self.scm.mark_files_updated((os.path.join(subdir, 'CMakeLists.txt'),))
- print("Careful: 'gr_modtool disable' does not resolve dependencies.")
+ logger.warning("Careful: 'gr_modtool disable' does not resolve dependencies.")
diff --git a/gr-utils/python/modtool/modtool_info.py b/gr-utils/python/modtool/core/info.py
index 2952a9c572..488445df47 100644
--- a/gr-utils/python/modtool/modtool_info.py
+++ b/gr-utils/python/modtool/core/info.py
@@ -1,4 +1,5 @@
-# Copyright 2013 Free Software Foundation, Inc.
+#
+# Copyright 2013, 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -25,40 +26,24 @@ from __future__ import unicode_literals
import os
-from .modtool_base import ModTool, ModToolException
-from .util_functions import get_modname
+from ..tools import get_modname
+from .base import ModTool, ModToolException
class ModToolInfo(ModTool):
""" Return information about a given module """
name = 'info'
- description = 'Return informations about module.'
-
- def __init__(self):
- ModTool.__init__(self)
- self._directory = None
- self._python_readable = False
- self._suggested_dirs = None
-
- @staticmethod
- def setup_parser(parser):
- """ Initialise the option parser for 'gr_modtool info' """
- #args_group = parser.add_argument_group(title="Info options")
- parser.add_argument("--python-readable", action="store_true",
- help="Return the output in a format that's easier to read for Python scripts.")
- parser.add_argument("--suggested-dirs",
- help="Suggest typical include dirs if nothing better can be detected.")
- return parser
+ description = 'Return information about a given module.'
- def setup(self, options):
- # Won't call parent's setup(), because that's too chatty
- self._directory = options.directory
- self._python_readable = options.python_readable
- self._suggested_dirs = options.suggested_dirs
+ def __init__(self, python_readable=False, suggested_dirs=None, **kwargs):
+ ModTool.__init__(self, **kwargs)
+ # Don't call ModTool._validate(), is is too chatty!
+ self._directory = self.dir
+ self._python_readable = python_readable
+ self._suggested_dirs = suggested_dirs
- def run(self, options):
+ def run(self):
""" Go, go, go! """
- self.setup(options)
mod_info = dict()
mod_info['base_dir'] = self._get_base_dir(self._directory)
if mod_info['base_dir'] is None:
@@ -67,13 +52,13 @@ class ModToolInfo(ModTool):
mod_info['modname'] = get_modname()
if mod_info['modname'] is None:
raise ModToolException('{}' if self._python_readable else "No module found.")
- if self._info['version'] == '36' and (
+ if self.info['version'] == '36' and (
os.path.isdir(os.path.join('include', mod_info['modname'])) or
os.path.isdir(os.path.join('include', 'gnuradio', mod_info['modname']))
- ):
- self._info['version'] = '37'
- mod_info['version'] = self._info['version']
- if 'is_component' in list(self._info.keys()) and self._info['is_component']:
+ ):
+ self.info['version'] = '37'
+ mod_info['version'] = self.info['version']
+ if 'is_component' in list(self.info.keys()) and self.info['is_component']:
mod_info['is_component'] = True
mod_info['incdirs'] = []
mod_incl_dir = os.path.join(mod_info['base_dir'], 'include')
@@ -130,9 +115,8 @@ class ModToolInfo(ModTool):
try:
cmakecache_fid = open(os.path.join(mod_info['build_dir'], 'CMakeCache.txt'))
for line in cmakecache_fid:
- if line.find('GNURADIO_RUNTIME_INCLUDE_DIRS:%s' % path_or_internal) != -1:
- inc_dirs += line.replace('GNURADIO_RUNTIME_INCLUDE_DIRS:%s='
- % path_or_internal, '').strip().split(';')
+ if line.find('GNURADIO_RUNTIME_INCLUDE_DIRS:{}'.format(path_or_internal)) != -1:
+ inc_dirs += line.replace('GNURADIO_RUNTIME_INCLUDE_DIRS:{}='.format(path_or_internal), '').strip().split(';')
except IOError:
pass
if (not inc_dirs or inc_dirs.isspace) and self._suggested_dirs is not None:
@@ -148,11 +132,11 @@ class ModToolInfo(ModTool):
'incdirs': 'Include directories'}
for key in list(mod_info.keys()):
if key == 'version':
- print(" API version: %s" % {
+ print(" API version: {}".format({
'36': 'pre-3.7',
'37': 'post-3.7',
'38': 'post-3.8',
'autofoo': 'Autotools (pre-3.5)'
- }[mod_info['version']])
+ }[mod_info['version']]))
else:
print('%19s: %s' % (index_names[key], mod_info[key]))
diff --git a/gr-utils/python/modtool/modtool_makexml.py b/gr-utils/python/modtool/core/makeyaml.py
index a6c4110f80..7f80555338 100644
--- a/gr-utils/python/modtool/modtool_makexml.py
+++ b/gr-utils/python/modtool/core/makeyaml.py
@@ -1,5 +1,5 @@
#
-# Copyright 2013 Free Software Foundation, Inc.
+# Copyright 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -18,7 +18,7 @@
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
-""" Automatically create XML bindings for GRC from block code """
+""" Automatically create YAML bindings for GRC from block code """
from __future__ import print_function
from __future__ import absolute_import
@@ -27,48 +27,38 @@ from __future__ import unicode_literals
import os
import re
import glob
+import logging
-from .modtool_base import ModTool, ModToolException
-from .parser_cc_block import ParserCCBlock
-from .grc_xml_generator import GRCXMLGenerator
-from .cmakefile_editor import CMakeFileEditor
-from .util_functions import ask_yes_no
+from ..tools import ParserCCBlock, CMakeFileEditor, ask_yes_no, GRCYAMLGenerator
+from .base import ModTool, ModToolException
+logger = logging.getLogger(__name__)
-class ModToolMakeXML(ModTool):
- """ Make XML file for GRC block bindings """
- name = 'makexml'
- description = 'Generate XML files for GRC block bindings.'
- def __init__(self):
- ModTool.__init__(self)
+class ModToolMakeYAML(ModTool):
+ """ Make YAML file for GRC block bindings """
+ name = 'makeyaml'
+ description = 'Generate YAML files for GRC block bindings.'
- @staticmethod
- def setup_parser(parser):
- """ Initialise the option parser for 'gr_modtool makexml' """
- parser.usage = """%s
+ def __init__(self, blockname=None, **kwargs):
+ ModTool.__init__(self, blockname, **kwargs)
+ self.info['pattern'] = blockname
- Note: This does not work on Python blocks!
- """ % parser.usage
- ModTool.setup_parser_block(parser)
+ def validate(self):
+ """ Validates the arguments """
+ ModTool._validate(self)
+ if not self.info['pattern'] or self.info['pattern'].isspace():
+ raise ModToolException("Incorrect blockname (Regex)!")
- def setup(self, options):
- ModTool.setup(self, options)
-
- if options.blockname is not None:
- self._info['pattern'] = options.blockname
- else:
- self._info['pattern'] = input('Which blocks do you want to parse? (Regex): ')
- if len(self._info['pattern']) == 0:
- self._info['pattern'] = '.'
-
- def run(self, options):
+ def run(self):
""" Go, go, go! """
- print("Warning: This is an experimental feature. Don't expect any magic.")
- self.setup(options)
+ # This portion will be covered by the CLI
+ if not self.cli:
+ self.validate()
+ logger.warning("Warning: This is an experimental feature. Don't expect any magic.")
# 1) Go through lib/
- if not self._skip_subdirs['lib']:
- if self._info['version'] in ('37', '38'):
+ if not self.skip_subdirs['lib']:
+ if self.info['version'] in ('37', '38'):
files = self._search_files('lib', '*_impl.cc')
else:
files = self._search_files('lib', '*.cc')
@@ -76,61 +66,61 @@ class ModToolMakeXML(ModTool):
if os.path.basename(f)[0:2] == 'qa':
continue
(params, iosig, blockname) = self._parse_cc_h(f)
- self._make_grc_xml_from_block_data(params, iosig, blockname)
+ self._make_grc_yaml_from_block_data(params, iosig, blockname)
# 2) Go through python/
# TODO
def _search_files(self, path, path_glob):
""" Search for files matching pattern in the given path. """
- files = sorted(glob.glob("%s/%s"% (path, path_glob)))
+ files = sorted(glob.glob("{}/{}".format(path, path_glob)))
files_filt = []
- print("Searching for matching files in %s/:" % path)
+ logger.info("Searching for matching files in {}/:".format(path))
for f in files:
- if re.search(self._info['pattern'], os.path.basename(f)) is not None:
+ if re.search(self.info['pattern'], os.path.basename(f)) is not None:
files_filt.append(f)
if len(files_filt) == 0:
- print("None found.")
+ logger.info("None found.")
return files_filt
- def _make_grc_xml_from_block_data(self, params, iosig, blockname):
- """ Take the return values from the parser and call the XML
- generator. Also, check the makefile if the .xml file is in there.
+ def _make_grc_yaml_from_block_data(self, params, iosig, blockname):
+ """ Take the return values from the parser and call the YAML
+ generator. Also, check the makefile if the .yml file is in there.
If necessary, add. """
- fname_xml = '%s_%s.xml' % (self._info['modname'], blockname)
- path_to_xml = os.path.join('grc', fname_xml)
+ fname_yml = '{}_{}.block.yml'.format(self.info['modname'], blockname)
+ path_to_yml = os.path.join('grc', fname_yml)
# Some adaptions for the GRC
for inout in ('in', 'out'):
if iosig[inout]['max_ports'] == '-1':
- iosig[inout]['max_ports'] = '$num_%sputs' % inout
- params.append({'key': 'num_%sputs' % inout,
+ iosig[inout]['max_ports'] = '$num_{}puts'.format(inout)
+ params.append({'key': 'num_{}puts'.format(inout),
'type': 'int',
- 'name': 'Num %sputs' % inout,
+ 'name': 'Num {}puts'.format(inout),
'default': '2',
'in_constructor': False})
file_exists = False
- if os.path.isfile(path_to_xml):
- if not self._info['yes']:
+ if os.path.isfile(path_to_yml):
+ if not self.info['yes']:
if not ask_yes_no('Overwrite existing GRC file?', False):
return
else:
file_exists = True
- print("Warning: Overwriting existing GRC file.")
- grc_generator = GRCXMLGenerator(
- modname=self._info['modname'],
- blockname=blockname,
- params=params,
- iosig=iosig
+ logger.warning("Warning: Overwriting existing GRC file.")
+ grc_generator = GRCYAMLGenerator(
+ modname=self.info['modname'],
+ blockname=blockname,
+ params=params,
+ iosig=iosig
)
- grc_generator.save(path_to_xml)
+ grc_generator.save(path_to_yml)
if file_exists:
- self.scm.mark_files_updated((path_to_xml,))
+ self.scm.mark_files_updated((path_to_yml,))
else:
- self.scm.add_files((path_to_xml,))
- if not self._skip_subdirs['grc']:
+ self.scm.add_files((path_to_yml,))
+ if not self.skip_subdirs['grc']:
ed = CMakeFileEditor(self._file['cmgrc'])
- if re.search(fname_xml, ed.cfile) is None and not ed.check_for_glob('*.xml'):
- print("Adding GRC bindings to grc/CMakeLists.txt...")
- ed.append_value('install', fname_xml, to_ignore_end='DESTINATION[^()]+')
+ if re.search(fname_yml, ed.cfile) is None and not ed.check_for_glob('*.yml'):
+ logger.info("Adding GRC bindings to grc/CMakeLists.txt...")
+ ed.append_value('install', fname_yml, to_ignore_end='DESTINATION[^()]+')
ed.write()
self.scm.mark_files_updated(self._file['cmgrc'])
@@ -159,16 +149,16 @@ class ModToolMakeXML(ModTool):
""" Return the block name and the header file name from the .cc file name """
blockname = os.path.splitext(os.path.basename(fname_cc.replace('_impl.', '.')))[0]
fname_h = (blockname + '.h').replace('_impl.', '.')
- blockname = blockname.replace(self._info['modname']+'_', '', 1)
+ blockname = blockname.replace(self.info['modname']+'_', '', 1)
return (blockname, fname_h)
# Go, go, go
- print("Making GRC bindings for %s..." % fname_cc)
+ logger.info("Making GRC bindings for {}...".format(fname_cc))
(blockname, fname_h) = _get_blockdata(fname_cc)
try:
parser = ParserCCBlock(fname_cc,
- os.path.join(self._info['includedir'], fname_h),
+ os.path.join(self.info['includedir'], fname_h),
blockname,
- self._info['version'],
+ self.info['version'],
_type_translate
)
except IOError:
diff --git a/gr-utils/python/modtool/core/newmod.py b/gr-utils/python/modtool/core/newmod.py
new file mode 100644
index 0000000000..fbe1731783
--- /dev/null
+++ b/gr-utils/python/modtool/core/newmod.py
@@ -0,0 +1,103 @@
+#
+# Copyright 2013, 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Create a whole new out-of-tree module """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import shutil
+import os
+import re
+import logging
+
+from gnuradio import gr
+from ..tools import SCMRepoFactory
+from .base import ModTool, ModToolException
+
+logger = logging.getLogger(__name__)
+
+
+class ModToolNewModule(ModTool):
+ """ Create a new out-of-tree module """
+ name = 'newmod'
+ description = 'Create new empty module, use add to add blocks.'
+ def __init__(self, module_name=None, srcdir=None, **kwargs):
+ ModTool.__init__(self, None, module_name, **kwargs)
+ # Don't call ModTool._validate(), that assumes an existing module.
+ self.srcdir = srcdir
+ self.directory = self.dir
+
+ def assign(self):
+ self.dir = os.path.join(self.directory, 'gr-{}'.format(self.info['modname']))
+ if self.srcdir is None:
+ self.srcdir = '/usr/local/share/gnuradio/modtool/templates/gr-newmod'
+ self.srcdir = gr.prefs().get_string('modtool', 'newmod_path', self.srcdir)
+
+ def validate(self):
+ """ Validates the arguments """
+ if self.info['modname'] is None:
+ raise ModToolException('Module name not specified.')
+ if not re.match('[a-zA-Z0-9_]+$', self.info['modname']):
+ raise ModToolException('Invalid module name.')
+ try:
+ os.stat(self.dir)
+ except OSError:
+ pass # This is what should happen
+ else:
+ raise ModToolException('The given directory exists.')
+ if not os.path.isdir(self.srcdir):
+ raise ModToolException('Could not find gr-newmod source dir.')
+
+ def run(self):
+ """
+ * Copy the example dir recursively
+ * Open all files, rename howto and HOWTO to the module name
+ * Rename files and directories that contain the word howto
+ """
+ # This portion will be covered by the CLI
+ if not self.cli:
+ self.assign()
+ self.validate()
+ self._setup_scm(mode='new')
+ logger.info("Creating out-of-tree module in {}...".format(self.dir))
+ try:
+ shutil.copytree(self.srcdir, self.dir)
+ os.chdir(self.dir)
+ except OSError:
+ raise ModToolException('Could not create directory {}.'.format(self.dir))
+ for root, dirs, files in os.walk('.'):
+ for filename in files:
+ f = os.path.join(root, filename)
+ with open(f, 'r') as filetext:
+ s = filetext.read()
+ s = s.replace('howto', self.info['modname'])
+ s = s.replace('HOWTO', self.info['modname'].upper())
+ with open(f, 'w') as filetext:
+ filetext.write(s)
+ if filename.find('howto') != -1:
+ os.rename(f, os.path.join(root, filename.replace('howto', self.info['modname'])))
+ if os.path.basename(root) == 'howto':
+ os.rename(root, os.path.join(os.path.dirname(root), self.info['modname']))
+ logger.info("Done.")
+ if self.scm.init_repo(path_to_repo="."):
+ logger.info("Created repository... you might want to commit before continuing.")
+ logger.info("Use 'gr_modtool add' to add a new block to this currently empty module.")
diff --git a/gr-utils/python/modtool/modtool_rename.py b/gr-utils/python/modtool/core/rename.py
index 60460680e3..8eec0ee8ec 100644
--- a/gr-utils/python/modtool/modtool_rename.py
+++ b/gr-utils/python/modtool/core/rename.py
@@ -1,5 +1,5 @@
#
-# Copyright 2014 Free Software Foundation, Inc.
+# Copyright 2014, 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -26,13 +26,11 @@ from __future__ import unicode_literals
import os
import re
-import glob
+import logging
-from .util_functions import append_re_line_sequence, ask_yes_no
-from .util_functions import SequenceCompleter
-from .cmakefile_editor import CMakeFileEditor
-from .modtool_base import ModTool, ModToolException
-from .templates import Templates
+from .base import get_block_candidates, ModTool, ModToolException
+
+logger = logging.getLogger(__name__)
def get_block_candidates():
cpp_filters = ["*.cc", "*.cpp"]
@@ -52,78 +50,61 @@ def get_block_candidates():
class ModToolRename(ModTool):
""" Rename a block in the out-of-tree module. """
name = 'rename'
- description = 'Rename block inside module.'
-
- def __init__(self):
- ModTool.__init__(self)
- self._add_cc_qa = False
- self._add_py_qa = False
- self._skip_cmakefiles = False
- self._license_file = None
-
- @staticmethod
- def setup_parser(parser):
- #parser = parser.add_argument_group(title="Rename module options")
- ModTool.setup_parser_block(parser)
- parser.add_argument("new_name", nargs="?", metavar='NEW-BLOCK-NAME',
- help="New name of the block.")
- return parser
-
- def setup(self, options):
- ModTool.setup(self, options)
-
- if ((self._skip_subdirs['lib'] and self._info['lang'] == 'cpp')
- or (self._skip_subdirs['python'] and self._info['lang'] == 'python')):
- raise ModToolException('Missing or skipping relevant subdir.')
-
- # first make sure the old block name is provided
- self._info['oldname'] = options.blockname
- if self._info['oldname'] is None:
- self._info['oldname'] = input("Enter name of block/code to rename (without module name prefix): ")
- else:
- block_candidates = get_block_candidates()
- with SequenceCompleter(block_candidates):
- self._info['oldname'] = \
- raw_input("Enter name of block/code to rename (without module name prefix): ")
- if not re.match('[a-zA-Z0-9_]+', self._info['oldname']):
- raise ModToolException('Invalid block name.')
- print("Block/code to rename identifier: " + self._info['oldname'])
- self._info['fulloldname'] = self._info['modname'] + '_' + self._info['oldname']
-
- # now get the new block name
- if options.new_name is None:
- self._info['newname'] = input("Enter name of block/code (without module name prefix): ")
- else:
- self._info['newname'] = options.new_name[0]
- if not re.match('[a-zA-Z0-9_]+', self._info['newname']):
+ description = 'Rename a block inside a module.'
+
+ def __init__(self, blockname=None, new_name=None, **kwargs):
+ ModTool.__init__(self, blockname, **kwargs)
+ self.info['oldname'] = blockname
+ self.info['newname'] = new_name
+
+ def validate(self):
+ """ Validates the arguments """
+ ModTool._validate(self)
+ if not self.info['oldname']:
+ raise ModToolException('Old block name (blockname) not specified.')
+ if not re.match('[a-zA-Z0-9_]+', self.info['oldname']):
raise ModToolException('Invalid block name.')
- print("Block/code identifier: " + self._info['newname'])
- self._info['fullnewname'] = self._info['modname'] + '_' + self._info['newname']
-
- def run(self, options):
+ block_candidates = get_block_candidates()
+ if self.info['oldname'] not in block_candidates:
+ choices = [x for x in block_candidates if self.info['oldname'] in x]
+ if len(choices)>0:
+ print("Suggested alternatives: "+str(choices))
+ raise ModToolException("Blockname for renaming does not exists!")
+ if not self.info['newname']:
+ raise ModToolException('New blockname (new_name) not specified.')
+ if not re.match('[a-zA-Z0-9_]+', self.info['newname']):
+ raise ModToolException('Invalid new block name.')
+
+ def assign(self):
+ self.info['fullnewname'] = self.info['modname'] + '_' + self.info['newname']
+
+ def run(self):
""" Go, go, go. """
- self.setup(options)
- module = self._info['modname']
- oldname = self._info['oldname']
- newname = self._info['newname']
- print("In module '%s' rename block '%s' to '%s'" % (module, oldname, newname))
+ # This portion will be covered by the CLI
+ if not self.cli:
+ self.validate()
+ self.assign()
+ module = self.info['modname']
+ oldname = self.info['oldname']
+ newname = self.info['newname']
+ logger.info("In module '{}' rename block '{}' to '{}'".format(module, oldname, newname))
self._run_swig_rename(self._file['swig'], oldname, newname)
- self._run_grc_rename(self._info['modname'], oldname, newname)
- self._run_python_qa(self._info['modname'], oldname, newname)
- self._run_python(self._info['modname'], oldname, newname)
- self._run_lib(self._info['modname'], oldname, newname)
- self._run_include(self._info['modname'], oldname, newname)
+ self._run_grc_rename(self.info['modname'], oldname, newname)
+ self._run_python_qa(self.info['modname'], oldname, newname)
+ self._run_python(self.info['modname'], oldname, newname)
+ self._run_lib(self.info['modname'], oldname, newname)
+ self._run_include(self.info['modname'], oldname, newname)
return
def _run_swig_rename(self, swigfilename, old, new):
""" Rename SWIG includes and block_magic """
nsubs = self._run_file_replace(swigfilename, old, new)
if nsubs < 1:
- print("Couldn't find '%s' in file '%s'." % (old, swigfilename))
+ logger.info("Couldn't find '{}' in file '{}'.".format(old, swigfilename))
if nsubs == 2:
- print("Changing 'noblock' type file")
+ logger.info("Changing 'noblock' type file")
if nsubs > 3:
- print("Hm, changed more then expected while editing %s." % swigfilename)
+ logger.warning("Hm, changed more then expected while editing {}.".format(swigfilename))
return False
def _run_lib(self, module, old, new):
@@ -143,7 +124,7 @@ class ModToolRename(ModTool):
filename = 'qa_' + module + '.cc'
nsubs = self._run_file_replace(path + filename, old, new)
if nsubs > 0:
- print("C++ QA code detected, renaming...")
+ logger.info("C++ QA code detected, renaming...")
filename = 'qa_' + old + '.cc'
self._run_file_replace(path + filename, old, new)
filename = 'qa_' + old + '.h'
@@ -151,7 +132,7 @@ class ModToolRename(ModTool):
self._run_file_replace(path + filename, old.upper(), new.upper())
self._run_file_rename(path, 'qa_' + old, 'qa_' + new)
else:
- print("No C++ QA code detected, skipping...")
+ logger.info("No C++ QA code detected, skipping...")
def _run_include(self, module, old, new):
path = './include/' + module + '/'
@@ -166,13 +147,13 @@ class ModToolRename(ModTool):
filename = '__init__.py'
nsubs = self._run_file_replace(path + filename, old, new)
if nsubs > 0:
- print("Python block detected, renaming...")
+ logger.info("Python block detected, renaming...")
filename = old + '.py'
self._run_file_replace(path + filename, old, new)
self._run_cmakelists(path, old, new)
self._run_file_rename(path, old, new)
else:
- print("Not a Python block, nothing to do here...")
+ logger.info("Not a Python block, nothing to do here...")
def _run_python_qa(self, module, old, new):
new = 'qa_' + new
@@ -183,7 +164,7 @@ class ModToolRename(ModTool):
self._run_file_rename('./python/', old, new)
def _run_grc_rename(self, module, old, new):
- grcfile = './grc/' + module + '_' + old + '.xml'
+ grcfile = './grc/' + module + '_' + old + '.yml'
self._run_file_replace(grcfile, old, new)
self._run_cmakelists('./grc/', old, new)
self._run_file_rename('./grc/', module + '_' + old, module + '_' + new)
@@ -192,7 +173,7 @@ class ModToolRename(ModTool):
filename = path + 'CMakeLists.txt'
nsubs = self._run_file_replace(filename, first, second)
if nsubs < 1:
- print("'%s' wasn't in '%s'." % (first, filename))
+ logger.info("'{}' wasn't in '{}'.".format(first, filename))
def _run_file_rename(self, path, old, new):
files = os.listdir(path)
@@ -201,18 +182,20 @@ class ModToolRename(ModTool):
nl = file.replace(old, new)
src = path + file
dst = path + nl
- print("Renaming file '%s' to '%s'." % (src, dst))
+ logger.info("Renaming file '{}' to '{}'.".format(src, dst))
os.rename(src, dst)
def _run_file_replace(self, filename, old, new):
if not os.path.isfile(filename):
return False
else:
- print("In '%s' renaming occurrences of '%s' to '%s'" % (filename, old, new))
+ logger.info("In '{}' renaming occurences of '{}' to '{}'".format(filename, old, new))
- cfile = open(filename).read()
+ with open(filename) as f:
+ cfile = f.read()
(cfile, nsubs) = re.subn(old, new, cfile)
- open(filename, 'w').write(cfile)
+ with open(filename, 'w') as f:
+ f.write(cfile)
self.scm.mark_file_updated(filename)
return nsubs
diff --git a/gr-utils/python/modtool/modtool_rm.py b/gr-utils/python/modtool/core/rm.py
index 55598a5090..688aa59835 100644
--- a/gr-utils/python/modtool/modtool_rm.py
+++ b/gr-utils/python/modtool/core/rm.py
@@ -1,5 +1,5 @@
#
-# Copyright 2013 Free Software Foundation, Inc.
+# Copyright 2013, 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -28,57 +28,54 @@ import os
import re
import sys
import glob
+import logging
-from .util_functions import remove_pattern_from_file, SequenceCompleter
-from .modtool_base import ModTool
-from .cmakefile_editor import CMakeFileEditor
+from ..tools import remove_pattern_from_file, CMakeFileEditor
+from ..cli import cli_input
+from .base import ModTool, ModToolException
+
+logger = logging.getLogger(__name__)
class ModToolRemove(ModTool):
""" Remove block (delete files and remove Makefile entries) """
name = 'remove'
- description = 'Remove block from module.'
-
- def __init__(self):
- ModTool.__init__(self)
-
- @staticmethod
- def setup_parser(parser):
- ModTool.setup_parser_block(parser)
+ description = 'Remove a block from a module.'
- def setup(self, options):
- ModTool.setup(self, options)
+ def __init__(self, blockname=None, **kwargs):
+ ModTool.__init__(self, blockname, **kwargs)
+ self.info['pattern'] = blockname
- if options.blockname is not None:
- self._info['pattern'] = options.blockname
- else:
- block_candidates = self.get_block_candidates()
- with SequenceCompleter(block_candidates):
- self._info['pattern'] = input('Which blocks do you want to delete? (Regex): ')
- if not self._info['pattern'] or self._info['pattern'].isspace():
- self._info['pattern'] = '.'
+ def validate(self):
+ """ Validates the arguments """
+ ModTool._validate(self)
+ if not self.info['pattern'] or self.info['pattern'].isspace():
+ raise ModToolException("Incorrect blockname (Regex)!")
- def run(self, options):
+ def run(self):
""" Go, go, go! """
+ # This portion will be covered by the CLI
+ if not self.cli:
+ self.validate()
def _remove_cc_test_case(filename=None, ed=None):
""" Special function that removes the occurrences of a qa*.cc file
from the CMakeLists.txt. """
if filename[:2] != 'qa':
return
- if self._info['version'] == '37':
+ if self.info['version'] == '37':
(base, ext) = os.path.splitext(filename)
if ext == '.h':
remove_pattern_from_file(self._file['qalib'],
- r'^#include "%s"\s*$' % filename)
+ r'^#include "{}"\s*$'.format(filename))
remove_pattern_from_file(self._file['qalib'],
- r'^\s*s->addTest\(gr::%s::%s::suite\(\)\);\s*$' % (
- self._info['modname'], base)
+ r'^\s*s->addTest\(gr::{}::{}::suite\(\)\);\s*$'.format(
+ self.info['modname'], base)
)
self.scm.mark_file_updated(self._file['qalib'])
elif ext == '.cc':
ed.remove_value('list',
r'\$\{CMAKE_CURRENT_SOURCE_DIR\}/%s' % filename,
- to_ignore_start='APPEND test_%s_sources' % self._info['modname'])
+ to_ignore_start='APPEND test_{}_sources'.format(self.info['modname']))
self.scm.mark_file_updated(ed.filename)
elif self._info['version'] == '38':
(base, ext) = os.path.splitext(filename)
@@ -106,36 +103,35 @@ class ModToolRemove(ModTool):
def _make_swig_regex(filename):
filebase = os.path.splitext(filename)[0]
- pyblockname = filebase.replace(self._info['modname'] + '_', '')
- regexp = r'(^\s*GR_SWIG_BLOCK_MAGIC2?\(%s,\s*%s\);|^\s*.include\s*"(%s/)?%s"\s*)' % \
- (self._info['modname'], pyblockname, self._info['modname'], filename)
+ pyblockname = filebase.replace(self.info['modname'] + '_', '')
+ regexp = r'(^\s*GR_SWIG_BLOCK_MAGIC2?\({},\s*{}\);|^\s*.include\s*"({}/)?{}"\s*)'.format \
+ (self.info['modname'], pyblockname, self.info['modname'], filename)
return regexp
# Go, go, go!
- self.setup(options)
- if not self._skip_subdirs['lib']:
+ if not self.skip_subdirs['lib']:
self._run_subdir('lib', ('*.cc', '*.h'), ('add_library', 'list'),
cmakeedit_func=_remove_cc_test_case)
- if not self._skip_subdirs['include']:
- incl_files_deleted = self._run_subdir(self._info['includedir'], ('*.h',), ('install',))
- if not self._skip_subdirs['swig']:
+ if not self.skip_subdirs['include']:
+ incl_files_deleted = self._run_subdir(self.info['includedir'], ('*.h',), ('install',))
+ if not self.skip_subdirs['swig']:
swig_files_deleted = self._run_subdir('swig', ('*.i',), ('install',))
for f in incl_files_deleted + swig_files_deleted:
# TODO do this on all *.i files
remove_pattern_from_file(self._file['swig'], _make_swig_regex(f))
self.scm.mark_file_updated(self._file['swig'])
- if not self._skip_subdirs['python']:
+ if not self.skip_subdirs['python']:
py_files_deleted = self._run_subdir('python', ('*.py',), ('GR_PYTHON_INSTALL',),
cmakeedit_func=_remove_py_test_case)
for f in py_files_deleted:
- remove_pattern_from_file(self._file['pyinit'], r'.*import\s+%s.*' % f[:-3])
- remove_pattern_from_file(self._file['pyinit'], r'.*from\s+%s\s+import.*\n' % f[:-3])
- if not self._skip_subdirs['grc']:
- self._run_subdir('grc', ('*.xml',), ('install',))
+ remove_pattern_from_file(self._file['pyinit'], r'.*import\s+{}.*'.format(f[:-3]))
+ remove_pattern_from_file(self._file['pyinit'], r'.*from\s+{}\s+import.*\n'.format(f[:-3]))
+ if not self.skip_subdirs['grc']:
+ self._run_subdir('grc', ('*.yml',), ('install',))
def _run_subdir(self, path, globs, makefile_vars, cmakeedit_func=None):
""" Delete all files that match a certain pattern in path.
path - The directory in which this will take place
- globs - A tuple of standard UNIX globs of files to delete (e.g. *.xml)
+ globs - A tuple of standard UNIX globs of files to delete (e.g. *.yml)
makefile_vars - A tuple with a list of CMakeLists.txt-variables which
may contain references to the globbed files
cmakeedit_func - If the CMakeLists.txt needs special editing, use this
@@ -143,23 +139,23 @@ class ModToolRemove(ModTool):
# 1. Create a filtered list
files = []
for g in globs:
- files = files + sorted(glob.glob("%s/%s"% (path, g)))
+ files = files + sorted(glob.glob("{}/{}".format(path, g)))
files_filt = []
- print("Searching for matching files in %s/:" % path)
+ logger.info("Searching for matching files in {}/:".format(path))
for f in files:
- if re.search(self._info['pattern'], os.path.basename(f)) is not None:
+ if re.search(self.info['pattern'], os.path.basename(f)) is not None:
files_filt.append(f)
if len(files_filt) == 0:
- print("None found.")
+ logger.info("None found.")
return []
# 2. Delete files, Makefile entries and other occurrences
files_deleted = []
- ed = CMakeFileEditor('%s/CMakeLists.txt' % path)
- yes = self._info['yes']
+ ed = CMakeFileEditor('{}/CMakeLists.txt'.format(path))
+ yes = self.info['yes']
for f in files_filt:
b = os.path.basename(f)
if not yes:
- ans = input("Really delete %s? [Y/n/a/q]: " % f).lower().strip()
+ ans = cli_input("Really delete {}? [Y/n/a/q]: ".format(f)).lower().strip()
if ans == 'a':
yes = True
if ans == 'q':
@@ -167,14 +163,14 @@ class ModToolRemove(ModTool):
if ans == 'n':
continue
files_deleted.append(b)
- print("Deleting %s." % f)
+ logger.info("Deleting {}.".format(f))
self.scm.remove_file(f)
os.unlink(f)
- print("Deleting occurrences of %s from %s/CMakeLists.txt..." % (b, path))
+ logger.info("Deleting occurrences of {} from {}/CMakeLists.txt...".format(b, path))
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(('%s/CMakeLists.txt' % path,))
+ self.scm.mark_files_updated(('{}/CMakeLists.txt'.format(path)))
return files_deleted
diff --git a/gr-utils/python/modtool/core/update.py b/gr-utils/python/modtool/core/update.py
new file mode 100644
index 0000000000..ec8a519d45
--- /dev/null
+++ b/gr-utils/python/modtool/core/update.py
@@ -0,0 +1,109 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" Module to convert XML bindings to YAML bindings """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import os
+import re
+import glob
+import logging
+
+from gnuradio.grc.converter import Converter
+from .base import ModTool, ModToolException
+
+logger = logging.getLogger(__name__)
+
+
+def get_xml_candidates():
+ """ Returns a list of XML candidates for update """
+ xml_candidates = []
+ xml_files = [x for x in glob.glob1("grc", "*.xml")]
+ for candidate in xml_files:
+ candidate = os.path.splitext(candidate)[0]
+ candidate = candidate.split("_", 1)[-1]
+ xml_candidates.append(candidate)
+ return xml_candidates
+
+
+class ModToolUpdate(ModTool):
+ """ Update the grc bindings for a block """
+ name = 'update'
+ description = 'Update the grc bindings for a block'
+
+ def __init__(self, blockname=None, complete=False, **kwargs):
+ ModTool.__init__(self, blockname, **kwargs)
+ self.info['complete'] = complete
+
+
+ def validate(self):
+ """ Validates the arguments """
+ ModTool._validate(self)
+ if self.info['complete']:
+ return
+ if not self.info['blockname'] or self.info['blockname'].isspace():
+ raise ModToolException('Block name not specified!')
+ block_candidates = get_xml_candidates()
+ if self.info['blockname'] not in block_candidates:
+ choices = [x for x in block_candidates if self.info['blockname'] in x]
+ if len(choices) > 0:
+ print("Suggested alternatives: "+str(choices))
+ raise ModToolException("The XML bindings does not exists!")
+
+ def run(self):
+ if not self.cli:
+ self.validate()
+ logger.warning("Warning: This is an experimental feature. Please verify the bindings.")
+ module_name = self.info['modname']
+ path = './grc/'
+ conv = Converter(path, path)
+ if self.info['complete']:
+ blocks = get_xml_candidates()
+ else:
+ blocks = [self.info['blockname']]
+ for blockname in blocks:
+ xml_file = "{}_{}.xml".format(module_name, blockname)
+ yml_file = "{}_{}.block.yml".format(module_name, blockname)
+ conv.load_block_xml(path+xml_file)
+ logger.info("Converted {} to {}".format(xml_file, yml_file))
+ os.remove(path+xml_file)
+ nsubs = self._run_cmakelists(xml_file, yml_file)
+ if nsubs > 1:
+ logger.warning("Changed more than expected for the block '%s' in the CMakeLists.txt. "
+ "Please verify the CMakeLists manually.", blockname)
+ elif nsubs == 0:
+ logger.warning("No entry found for the block '%s' in the CMakeLists.txt. "
+ 'Please verify the CMakeLists manually.', blockname)
+ else:
+ logger.info('Updated the CMakeLists.txt')
+
+ def _run_cmakelists(self, to_remove, to_add):
+ """ Changes in the CMakeLists """
+ filename = './grc/CMakeLists.txt'
+ with open(filename) as f:
+ cfile = f.read()
+ (cfile, nsubs) = re.subn(to_remove, to_add, cfile)
+ with open(filename, 'w') as f:
+ f.write(cfile)
+ self.scm.mark_file_updated(filename)
+ return nsubs
diff --git a/gr-utils/python/modtool/grc_xml_generator.py b/gr-utils/python/modtool/grc_xml_generator.py
deleted file mode 100644
index 1109701f7e..0000000000
--- a/gr-utils/python/modtool/grc_xml_generator.py
+++ /dev/null
@@ -1,105 +0,0 @@
-from __future__ import absolute_import
-#
-# Copyright 2013 Free Software Foundation, Inc.
-#
-# This file is part of GNU Radio
-#
-# GNU Radio is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3, or (at your option)
-# any later version.
-#
-# GNU Radio is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with GNU Radio; see the file COPYING. If not, write to
-# the Free Software Foundation, Inc., 51 Franklin Street,
-# Boston, MA 02110-1301, USA.
-#
-import xml.etree.ElementTree as ET
-from .util_functions import is_number, xml_indent
-
-try:
- import lxml.etree
- LXML_IMPORTED = True
-except ImportError:
- LXML_IMPORTED = False
-
-class GRCXMLGenerator(object):
- """ Create and write the XML bindings for a GRC block. """
- def __init__(self, modname=None, blockname=None, doc=None, params=None, iosig=None):
- """docstring for __init__"""
- params_list = ['$'+s['key'] for s in params if s['in_constructor']]
- # Can't make a dict 'cause order matters
- self._header = (('name', blockname.replace('_', ' ').capitalize()),
- ('key', '%s_%s' % (modname, blockname)),
- ('category', '[%s]' % modname.upper()),
- ('import', 'import %s' % modname),
- ('make', '%s.%s(%s)' % (modname, blockname, ', '.join(params_list)))
- )
- self.params = params
- self.iosig = iosig
- self.doc = doc
- self.root = None
- if LXML_IMPORTED:
- self._prettyprint = self._lxml_prettyprint
- else:
- self._prettyprint = self._manual_prettyprint
-
- def _lxml_prettyprint(self):
- """ XML pretty printer using lxml """
- return lxml.etree.tostring(
- lxml.etree.fromstring(ET.tostring(self.root, encoding="UTF-8")),
- pretty_print=True
- )
-
- def _manual_prettyprint(self):
- """ XML pretty printer using xml_indent """
- xml_indent(self.root)
- return ET.tostring(self.root, encoding="UTF-8")
-
- def make_xml(self):
- """ Create the actual tag tree """
- root = ET.Element("block")
- iosig = self.iosig
- for tag, value in self._header:
- this_tag = ET.SubElement(root, tag)
- this_tag.text = value
- for param in self.params:
- param_tag = ET.SubElement(root, 'param')
- ET.SubElement(param_tag, 'name').text = param['key'].capitalize()
- ET.SubElement(param_tag, 'key').text = param['key']
- if len(param['default']):
- ET.SubElement(param_tag, 'value').text = param['default']
- ET.SubElement(param_tag, 'type').text = param['type']
- for inout in sorted(iosig.keys()):
- if iosig[inout]['max_ports'] == '0':
- continue
- for i in range(len(iosig[inout]['type'])):
- s_tag = ET.SubElement(root, {'in': 'sink', 'out': 'source'}[inout])
- ET.SubElement(s_tag, 'name').text = inout
- ET.SubElement(s_tag, 'type').text = iosig[inout]['type'][i]
- if iosig[inout]['vlen'][i] != '1':
- vlen = iosig[inout]['vlen'][i]
- if is_number(vlen):
- ET.SubElement(s_tag, 'vlen').text = vlen
- else:
- ET.SubElement(s_tag, 'vlen').text = '$'+vlen
- if i == len(iosig[inout]['type'])-1:
- if not is_number(iosig[inout]['max_ports']):
- ET.SubElement(s_tag, 'nports').text = iosig[inout]['max_ports']
- elif len(iosig[inout]['type']) < int(iosig[inout]['max_ports']):
- ET.SubElement(s_tag, 'nports').text = str(int(iosig[inout]['max_ports']) -
- len(iosig[inout]['type'])+1)
- if self.doc is not None:
- ET.SubElement(root, 'doc').text = self.doc
- self.root = root
-
- def save(self, filename):
- """ Write the XML file """
- self.make_xml()
- open(filename, 'w').write(self._prettyprint())
-
diff --git a/gr-utils/python/modtool/modtool_add.py b/gr-utils/python/modtool/modtool_add.py
deleted file mode 100644
index 9f128d2fde..0000000000
--- a/gr-utils/python/modtool/modtool_add.py
+++ /dev/null
@@ -1,367 +0,0 @@
-#
-# Copyright 2013, 2017-2018 Free Software Foundation, Inc.
-#
-# This file is part of GNU Radio
-#
-# GNU Radio is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3, or (at your option)
-# any later version.
-#
-# GNU Radio is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with GNU Radio; see the file COPYING. If not, write to
-# the Free Software Foundation, Inc., 51 Franklin Street,
-# Boston, MA 02110-1301, USA.
-#
-""" Module to add new blocks """
-
-from __future__ import print_function
-from __future__ import absolute_import
-from __future__ import unicode_literals
-
-from builtins import input
-
-import os
-import re
-import getpass
-
-from .util_functions import append_re_line_sequence, ask_yes_no, SequenceCompleter
-from .cmakefile_editor import CMakeFileEditor
-from .modtool_base import ModTool, ModToolException
-from .templates import Templates
-from .code_generator import render_template
-
-class ModToolAdd(ModTool):
- """ Add block to the out-of-tree module. """
- name = 'add'
- description = 'Add new block into module.'
- _block_types = ('sink', 'source', 'sync', 'decimator', 'interpolator',
- 'general', 'tagged_stream', 'hier', 'noblock')
-
- def __init__(self):
- ModTool.__init__(self)
- self._add_cc_qa = False
- self._add_py_qa = False
- self._skip_cmakefiles = False
- self._license_file = None
-
- @staticmethod
- def setup_parser(parser):
- parser.add_argument("-t", "--block-type", choices=ModToolAdd._block_types,
- help="One of %s." % ', '.join(ModToolAdd._block_types))
- parser.add_argument("--license-file",
- help="File containing the license header for every source code file.")
- parser.add_argument("--copyright",
- help="Name of the copyright holder (you or your company) MUST be a quoted string.")
- parser.add_argument("--argument-list",
- help="The argument list for the constructor and make functions.")
- parser.add_argument("--add-python-qa", action="store_true", default=None,
- help="If given, Python QA code is automatically added if possible.")
- parser.add_argument("--add-cpp-qa", action="store_true", default=None,
- help="If given, C++ QA code is automatically added if possible.")
- parser.add_argument("--skip-cmakefiles", action="store_true",
- help="If given, only source files are written, but CMakeLists.txt files are left unchanged.")
- parser.add_argument("-l", "--lang", choices=('cpp', 'c++', 'python'),
- help="Programing language")
- ModTool.setup_parser_block(parser)
- return parser
-
- def setup(self, options):
- ModTool.setup(self, options)
-
- if self._info['blockname'] is None:
- if len(options) >= 2:
- self._info['blockname'] = options[1]
- else:
- self._info['blockname'] = input("Enter name of block/code (without module name prefix): ")
- if os.path.isfile("./lib/"+self._info['blockname']+"_impl.cc") or os.path.isfile("./python/"+self._info['blockname']+".py"):
- raise ModToolException('The given blockname already exists!')
- if not re.match('[a-zA-Z0-9_]+', self._info['blockname']):
- raise ModToolException('Invalid block name.')
- print("Block/code identifier: " + self._info['blockname'])
- self._info['fullblockname'] = self._info['modname'] + '_' + self._info['blockname']
-
- self._info['blocktype'] = options.block_type
- if self._info['blocktype'] is None:
- # Print list out of blocktypes to user for reference
- print(str(self._block_types))
- with SequenceCompleter(sorted(self._block_types)):
- while self._info['blocktype'] not in self._block_types:
- self._info['blocktype'] = input("Enter block type: ")
- if self._info['blocktype'] not in self._block_types:
- print('Must be one of ' + str(self._block_types))
-
- # Allow user to specify language interactively if not set
- self._info['lang'] = options.lang
- if self._info['lang'] is None:
- language_candidates = ('c++', 'cpp', 'python')
- with SequenceCompleter(language_candidates):
- while self._info['lang'] not in language_candidates:
- self._info['lang'] = input("Language (python/cpp): ")
- if self._info['lang'] == 'c++':
- self._info['lang'] = 'cpp'
-
- print("Language: %s" % {'cpp': 'C++', 'python': 'Python'}[self._info['lang']])
-
- if ((self._skip_subdirs['lib'] and self._info['lang'] == 'cpp')
- or (self._skip_subdirs['python'] and self._info['lang'] == 'python')):
- raise ModToolException('Missing or skipping relevant subdir.')
-
- if self._info['blockname'] is None:
- self._info['blockname'] = input("Enter name of block/code (without module name prefix): ")
- if not re.match('[a-zA-Z0-9_]+', self._info['blockname']):
- raise ModToolException('Invalid block name.')
- print("Block/code identifier: " + self._info['blockname'])
- self._info['fullblockname'] = self._info['modname'] + '_' + self._info['blockname']
- if not options.license_file:
- self._info['copyrightholder'] = options.copyright
- if self._info['copyrightholder'] is None:
- user = getpass.getuser()
- git_user = self.scm.get_gituser()
- if git_user:
- copyright_candidates = (user, git_user, 'GNU Radio')
- else:
- copyright_candidates = (user, 'GNU Radio')
- with SequenceCompleter(copyright_candidates):
- self._info['copyrightholder'] = input("Please specify the copyright holder: ")
- if not self._info['copyrightholder'] or self._info['copyrightholder'].isspace():
- self._info['copyrightholder'] = "gr-"+self._info['modname']+" author"
- elif self._info['is_component']:
- print("For GNU Radio components the FSF is added as copyright holder")
- self._license_file = options.license_file
- self._info['license'] = self.setup_choose_license()
- if options.argument_list is not None:
- self._info['arglist'] = options.argument_list
- else:
- self._info['arglist'] = input('Enter valid argument list, including default arguments: ')
-
- if not (self._info['blocktype'] in ('noblock') or self._skip_subdirs['python']):
- self._add_py_qa = options.add_python_qa
- if self._add_py_qa is None:
- self._add_py_qa = ask_yes_no('Add Python QA code?', True)
- if self._info['lang'] == 'cpp':
- self._add_cc_qa = options.add_cpp_qa
- if self._add_cc_qa is None:
- self._add_cc_qa = ask_yes_no('Add C++ QA code?', not self._add_py_qa)
- self._skip_cmakefiles = options.skip_cmakefiles
- if self._info['version'] == 'autofoo' and not self._skip_cmakefiles:
- print("Warning: Autotools modules are not supported. ",
- "Files will be created, but Makefiles will not be edited.")
- self._skip_cmakefiles = True
-
- def setup_choose_license(self):
- """ Select a license by the following rules, in this order:
- 1) The contents of the file given by --license-file
- 2) The contents of the file LICENSE or LICENCE in the modules
- top directory
- 3) The default license. """
- if self._license_file is not None \
- and os.path.isfile(self._license_file):
- return open(self._license_file).read()
- elif os.path.isfile('LICENSE'):
- return open('LICENSE').read()
- elif os.path.isfile('LICENCE'):
- return open('LICENCE').read()
- elif self._info['is_component']:
- return Templates['grlicense']
- else:
- return Templates['defaultlicense'].format(**self._info)
-
- def _write_tpl(self, tpl, path, fname):
- """ Shorthand for writing a substituted template to a file"""
- path_to_file = os.path.join(path, fname)
- print("Adding file '%s'..." % path_to_file)
- open(path_to_file, 'w').write(render_template(tpl, **self._info))
- self.scm.add_files((path_to_file,))
-
- def run(self, options):
- """ Go, go, go. """
- self.setup(options)
- has_swig = (
- self._info['lang'] == 'cpp'
- and not self._skip_subdirs['swig']
- )
- has_grc = False
- if self._info['lang'] == 'cpp':
- self._run_lib()
- has_grc = has_swig
- else: # Python
- self._run_python()
- if self._info['blocktype'] != 'noblock':
- has_grc = True
- if has_swig:
- self._run_swig()
- if self._add_py_qa:
- self._run_python_qa()
- if has_grc and not self._skip_subdirs['grc']:
- self._run_grc()
-
- def _run_lib(self):
- """ Do everything that needs doing in the subdir 'lib' and 'include'.
- - add .cc and .h files
- - include them into CMakeLists.txt
- - check if C++ QA code is req'd
- - if yes, create qa_*.{cc,h} and add them to CMakeLists.txt
- """
- def _add_qa():
- " Add C++ QA files for 3.7 API "
- fname_qa_h = 'qa_%s.h' % self._info['blockname']
- fname_qa_cc = 'qa_%s.cc' % self._info['blockname']
- self._write_tpl('qa_cpp', 'lib', fname_qa_cc)
- self._write_tpl('qa_h', 'lib', fname_qa_h)
- if not self._skip_cmakefiles:
- try:
- append_re_line_sequence(self._file['cmlib'],
- r'\$\{CMAKE_CURRENT_SOURCE_DIR\}/qa_%s.cc.*\n' % self._info['modname'],
- ' ${CMAKE_CURRENT_SOURCE_DIR}/qa_%s.cc' % self._info['blockname'])
- append_re_line_sequence(self._file['qalib'],
- '#include.*\n',
- '#include "%s"' % fname_qa_h)
- append_re_line_sequence(self._file['qalib'],
- '(addTest.*suite.*\n|new CppUnit.*TestSuite.*\n)',
- ' s->addTest(gr::%s::qa_%s::suite());' % (self._info['modname'],
- self._info['blockname'])
- )
- self.scm.mark_files_updated((self._file['qalib'],))
- except IOError:
- print("Can't add C++ QA files.")
- def _add_qa_boostutf():
- " Add C++ QA files for 3.8 API "
- fname_qa_cc = 'qa_%s.cc' % self._info['blockname']
- self._write_tpl('qa_cpp_boostutf', 'lib', fname_qa_cc)
- if not self._skip_cmakefiles:
- try:
- ed = CMakeFileEditor(self._file['cmlib'])
- cmake_list_var = \
- 'test_{}_source'.format(self._info['modname'])
- if not ed.append_value(
- 'list',
- fname_qa_cc,
- to_ignore_start='APPEND ' + cmake_list_var):
- print("Couldn't add C++ QA files.")
- ed.write()
- self.scm.mark_files_updated((self._file['cmlib'],))
- except IOError:
- print("Can't add C++ QA files.")
- fname_cc = None
- fname_h = None
- if self._info['version'] in ('37', '38'):
- fname_h = self._info['blockname'] + '.h'
- fname_cc = self._info['blockname'] + '.cc'
- if self._info['blocktype'] in ('source', 'sink', 'sync', 'decimator',
- 'interpolator', 'general', 'hier', 'tagged_stream'):
- fname_cc = self._info['blockname'] + '_impl.cc'
- self._write_tpl('block_impl_h', 'lib', self._info['blockname'] + '_impl.h')
- self._write_tpl('block_impl_cpp', 'lib', fname_cc)
- self._write_tpl('block_def_h', self._info['includedir'], fname_h)
- else: # Pre-3.7 or autotools
- fname_h = self._info['fullblockname'] + '.h'
- fname_cc = self._info['fullblockname'] + '.cc'
- self._write_tpl('block_h36', self._info['includedir'], fname_h)
- self._write_tpl('block_cpp36', 'lib', fname_cc)
- if self._add_cc_qa:
- if self._info['version'] == '37':
- _add_qa()
- if self._info['version'] == '38':
- _add_qa_boostutf()
- elif self._info['version'] == '36':
- print("Warning: C++ QA files not supported for 3.6-style OOTs.")
- elif self._info['version'] == 'autofoo':
- print("Warning: C++ QA files not supported for autotools.")
- if not self._skip_cmakefiles:
- ed = CMakeFileEditor(self._file['cmlib'])
- cmake_list_var = '[a-z]*_?' + self._info['modname'] + '_sources'
- if not ed.append_value('list', fname_cc, to_ignore_start='APPEND ' + cmake_list_var):
- ed.append_value('add_library', fname_cc)
- ed.write()
- ed = CMakeFileEditor(self._file['cminclude'])
- ed.append_value('install', fname_h, to_ignore_end='DESTINATION[^()]+')
- ed.write()
- self.scm.mark_files_updated((self._file['cminclude'], self._file['cmlib']))
-
- def _run_swig(self):
- """ Do everything that needs doing in the subdir 'swig'.
- - Edit main *.i file
- """
- if self._get_mainswigfile() is None:
- print('Warning: No main swig file found.')
- return
- print("Editing %s..." % self._file['swig'])
- mod_block_sep = '/'
- if self._info['version'] == '36':
- mod_block_sep = '_'
- swig_block_magic_str = render_template('swig_block_magic', **self._info)
- open(self._file['swig'], 'a').write(swig_block_magic_str)
- include_str = '#include "%s%s%s.h"' % (
- {True: 'gnuradio/' + self._info['modname'],
- False: self._info['modname']}[self._info['is_component']],
- mod_block_sep,
- self._info['blockname'])
- if re.search('#include', open(self._file['swig'], 'r').read()):
- append_re_line_sequence(self._file['swig'], '^#include.*\n', include_str)
- else: # I.e., if the swig file is empty
- oldfile = open(self._file['swig'], 'r').read()
- regexp = re.compile(r'^%\{\n', re.MULTILINE)
- oldfile = regexp.sub('%%{\n%s\n' % include_str, oldfile, count=1)
- open(self._file['swig'], 'w').write(oldfile)
- self.scm.mark_files_updated((self._file['swig'],))
-
- def _run_python_qa(self):
- """ Do everything that needs doing in the subdir 'python' to add
- QA code.
- - add .py files
- - include in CMakeLists.txt
- """
- fname_py_qa = 'qa_' + self._info['blockname'] + '.py'
- self._write_tpl('qa_python', self._info['pydir'], fname_py_qa)
- os.chmod(os.path.join(self._info['pydir'], fname_py_qa), 0o755)
- self.scm.mark_files_updated((os.path.join(self._info['pydir'], fname_py_qa),))
- if self._skip_cmakefiles or CMakeFileEditor(self._file['cmpython']).check_for_glob('qa_*.py'):
- return
- print("Editing %s/CMakeLists.txt..." % self._info['pydir'])
- open(self._file['cmpython'], 'a').write(
- 'GR_ADD_TEST(qa_%s ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/%s)\n' % \
- (self._info['blockname'], fname_py_qa))
- self.scm.mark_files_updated((self._file['cmpython'],))
-
- def _run_python(self):
- """ Do everything that needs doing in the subdir 'python' to add
- a Python block.
- - add .py file
- - include in CMakeLists.txt
- - include in __init__.py
- """
- fname_py = self._info['blockname'] + '.py'
- self._write_tpl('block_python', self._info['pydir'], fname_py)
- append_re_line_sequence(self._file['pyinit'],
- '(^from.*import.*\n|# import any pure.*\n)',
- 'from %s import %s' % (self._info['blockname'],
- self._info['blockname']))
- self.scm.mark_files_updated((self._file['pyinit'],))
- if self._skip_cmakefiles:
- return
- ed = CMakeFileEditor(self._file['cmpython'])
- ed.append_value('GR_PYTHON_INSTALL', fname_py, to_ignore_end='DESTINATION[^()]+')
- ed.write()
- self.scm.mark_files_updated((self._file['cmpython'],))
-
- def _run_grc(self):
- """ Do everything that needs doing in the subdir 'grc' to add
- a GRC bindings XML file.
- - add .xml file
- - include in CMakeLists.txt
- """
- fname_grc = self._info['fullblockname'] + '.xml'
- self._write_tpl('grc_xml', 'grc', fname_grc)
- ed = CMakeFileEditor(self._file['cmgrc'], '\n ')
- if self._skip_cmakefiles or ed.check_for_glob('*.xml'):
- return
- print("Editing grc/CMakeLists.txt...")
- ed.append_value('install', fname_grc, to_ignore_end='DESTINATION[^()]+')
- ed.write()
- self.scm.mark_files_updated((self._file['cmgrc'],))
diff --git a/gr-utils/python/modtool/modtool_base.py b/gr-utils/python/modtool/modtool_base.py
deleted file mode 100644
index 41d83ccafb..0000000000
--- a/gr-utils/python/modtool/modtool_base.py
+++ /dev/null
@@ -1,212 +0,0 @@
-#
-# Copyright 2013,2018 Free Software Foundation, Inc.
-#
-# This file is part of GNU Radio
-#
-# GNU Radio is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3, or (at your option)
-# any later version.
-#
-# GNU Radio is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with GNU Radio; see the file COPYING. If not, write to
-# the Free Software Foundation, Inc., 51 Franklin Street,
-# Boston, MA 02110-1301, USA.
-#
-""" Base class for the modules """
-
-from __future__ import print_function
-from __future__ import absolute_import
-from __future__ import unicode_literals
-
-import os
-import re
-from argparse import ArgumentParser, RawDescriptionHelpFormatter
-
-from gnuradio import gr
-from .util_functions import get_modname
-from .scm import SCMRepoFactory
-
-class ModToolException(BaseException):
- """ Standard exception for modtool classes. """
- pass
-
-class ModTool(object):
- """ Base class for all modtool command classes. """
- name = 'base'
- description = None
-
- def __init__(self):
- self._subdirs = ['lib', 'include', 'python', 'swig', 'grc'] # List subdirs where stuff happens
- self._has_subdirs = {}
- self._skip_subdirs = {}
- self._info = {}
- self._file = {}
- for subdir in self._subdirs:
- self._has_subdirs[subdir] = False
- self._skip_subdirs[subdir] = False
- self._dir = None
- self.scm = None
-
- @staticmethod
- def setup_parser(parser):
- """Override in child class."""
- pass
-
- @staticmethod
- def setup_parser_block(parser):
- """Setup options specific for block manipulating modules."""
- parser.add_argument("blockname", nargs="?", metavar="BLOCK_NAME",
- help="Name of the block/module")
-
- @staticmethod
- def get_parser():
- """Init the option parser."""
- parser = ArgumentParser(
- description='Manipulate with GNU Radio modules source code tree. ' + \
- 'Call it withou options to run specified command interactively',
- formatter_class=RawDescriptionHelpFormatter)
- parser.add_argument("-d", "--directory", default=".",
- help="Base directory of the module. Defaults to the cwd.")
- parser.add_argument("--skip-lib", action="store_true",
- help="Don't do anything in the lib/ subdirectory.")
- parser.add_argument("--skip-swig", action="store_true",
- help="Don't do anything in the swig/ subdirectory.")
- parser.add_argument("--skip-python", action="store_true",
- help="Don't do anything in the python/ subdirectory.")
- parser.add_argument("--skip-grc", action="store_true",
- help="Don't do anything in the grc/ subdirectory.")
- parser.add_argument("--scm-mode", choices=('yes', 'no', 'auto'),
- default=gr.prefs().get_string('modtool', 'scm_mode', 'no'),
- help="Use source control management [ yes | no | auto ]).")
- parser.add_argument("-y", "--yes", action="store_true",
- help="Answer all questions with 'yes'. This can overwrite and delete your files, so be careful.")
- return parser
-
- def setup(self, options):
- """ Initialise all internal variables, such as the module name etc. """
- self._dir = options.directory
- if not self._check_directory(self._dir):
- raise ModToolException('No GNU Radio module found in the given directory.')
- if hasattr(options, 'module_name') and options.module_name is not None:
- self._info['modname'] = options.module_name
- else:
- self._info['modname'] = get_modname()
- if self._info['modname'] is None:
- raise ModToolException('No GNU Radio module found in the given directory.')
- print("GNU Radio module name identified: " + self._info['modname'])
- if self._info['version'] == '36' and (
- os.path.isdir(os.path.join('include', self._info['modname'])) or
- os.path.isdir(os.path.join('include', 'gnuradio', self._info['modname']))
- ):
- self._info['version'] = '37'
- if not os.path.isfile(os.path.join('cmake', 'Modules', 'FindCppUnit.cmake')):
- self._info['version'] = '38'
- if options.skip_lib or not self._has_subdirs['lib']:
- self._skip_subdirs['lib'] = True
- if options.skip_python or not self._has_subdirs['python']:
- self._skip_subdirs['python'] = True
- if options.skip_swig or self._get_mainswigfile() is None or not self._has_subdirs['swig']:
- self._skip_subdirs['swig'] = True
- if options.skip_grc or not self._has_subdirs['grc']:
- self._skip_subdirs['grc'] = True
- self._info['blockname'] = options.blockname
- self._setup_files()
- self._info['yes'] = options.yes
- self.options = options
- self._setup_scm()
-
- def _setup_files(self):
- """ Initialise the self._file[] dictionary """
- if not self._skip_subdirs['swig']:
- self._file['swig'] = os.path.join('swig', self._get_mainswigfile())
- self._info['pydir'] = 'python'
- if os.path.isdir(os.path.join('python', self._info['modname'])):
- self._info['pydir'] = os.path.join('python', self._info['modname'])
- self._file['qalib'] = os.path.join('lib', 'qa_%s.cc' % self._info['modname'])
- self._file['pyinit'] = os.path.join(self._info['pydir'], '__init__.py')
- self._file['cmlib'] = os.path.join('lib', 'CMakeLists.txt')
- self._file['cmgrc'] = os.path.join('grc', 'CMakeLists.txt')
- self._file['cmpython'] = os.path.join(self._info['pydir'], 'CMakeLists.txt')
- if self._info['is_component']:
- self._info['includedir'] = os.path.join('include', 'gnuradio', self._info['modname'])
- elif self._info['version'] in ('37', '38'):
- self._info['includedir'] = os.path.join('include', self._info['modname'])
- else:
- self._info['includedir'] = 'include'
- self._file['cminclude'] = os.path.join(self._info['includedir'], 'CMakeLists.txt')
- self._file['cmswig'] = os.path.join('swig', 'CMakeLists.txt')
- self._file['cmfind'] = os.path.join('cmake', 'Modules', 'howtoConfig.cmake')
-
-
- def _setup_scm(self, mode='active'):
- """ Initialize source control management. """
- if mode == 'active':
- self.scm = SCMRepoFactory(self.options, '.').make_active_scm_manager()
- else:
- self.scm = SCMRepoFactory(self.options, '.').make_empty_scm_manager()
- if self.scm is None:
- print("Error: Can't set up SCM.")
- exit(1)
-
- def _check_directory(self, directory):
- """ Guesses if dir is a valid GNU Radio module directory by looking for
- CMakeLists.txt and at least one of the subdirs lib/, python/ and swig/.
- Changes the directory, if valid. """
- has_makefile = False
- try:
- files = os.listdir(directory)
- os.chdir(directory)
- except OSError:
- print("Can't read or chdir to directory %s." % directory)
- return False
- self._info['is_component'] = False
- for f in files:
- if os.path.isfile(f) and f == 'CMakeLists.txt':
- if re.search(r'find_package\(Gnuradio', open(f).read()) is not None:
- self._info['version'] = '36' # Might be 37, check that later
- has_makefile = True
- elif re.search('GR_REGISTER_COMPONENT', open(f).read()) is not None:
- self._info['version'] = '36' # Might be 37, check that later
- self._info['is_component'] = True
- has_makefile = True
- # TODO search for autofoo
- elif os.path.isdir(f):
- if (f in list(self._has_subdirs.keys())):
- self._has_subdirs[f] = True
- else:
- self._skip_subdirs[f] = True
- return bool(has_makefile and (list(self._has_subdirs.values())))
-
- def _get_mainswigfile(self):
- """ Find out which name the main SWIG file has. In particular, is it
- a MODNAME.i or a MODNAME_swig.i? Returns None if none is found. """
- modname = self._info['modname']
- swig_files = (modname + '.i',
- modname + '_swig.i')
- for fname in swig_files:
- if os.path.isfile(os.path.join(self._dir, 'swig', fname)):
- return fname
- return None
-
- def run(self, options):
- """ Override this. """
- raise NotImplementedError('Module implementation missing')
-
-
-def get_modtool_modules(all_objects):
- """Return list with all modtool modules."""
- modules = []
- for o in all_objects:
- try:
- if issubclass(o, ModTool) and o != ModTool:
- modules.append(o)
- except (TypeError, AttributeError):
- pass
- return modules
-
diff --git a/gr-utils/python/modtool/modtool_newmod.py b/gr-utils/python/modtool/modtool_newmod.py
deleted file mode 100644
index c283204ee2..0000000000
--- a/gr-utils/python/modtool/modtool_newmod.py
+++ /dev/null
@@ -1,104 +0,0 @@
-#
-# Copyright 2013 Free Software Foundation, Inc.
-#
-# This file is part of GNU Radio
-#
-# GNU Radio is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 3, or (at your option)
-# any later version.
-#
-# GNU Radio is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with GNU Radio; see the file COPYING. If not, write to
-# the Free Software Foundation, Inc., 51 Franklin Street,
-# Boston, MA 02110-1301, USA.
-#
-""" Create a whole new out-of-tree module """
-
-from __future__ import print_function
-from __future__ import absolute_import
-from __future__ import unicode_literals
-
-import shutil
-import os
-import re
-from gnuradio import gr
-from .modtool_base import ModTool, ModToolException
-from .scm import SCMRepoFactory
-
-class ModToolNewModule(ModTool):
- """ Create a new out-of-tree module """
- name = 'newmod'
- description = 'Create new empty module, use add to add blocks.'
- def __init__(self):
- ModTool.__init__(self)
-
- @staticmethod
- def setup_parser(parser):
- " Initialise the option parser for 'gr_modtool newmod' "
- parser.add_argument("--srcdir",
- help="Source directory for the module template.")
- parser.add_argument("module_name", metavar='MODULE-NAME', nargs='?',
- help="Override the current module's name (normally is autodetected).")
-
- def setup(self, options):
- # Don't call ModTool.setup(), that assumes an existing module.
- self._info['modname'] = options.module_name
- if self._info['modname'] is None:
- if options.module_name:
- self._info['modname'] = options.module_name
- else:
- self._info['modname'] = input('Name of the new module: ')
- if not re.match('[a-zA-Z0-9_]+$', self._info['modname']):
- raise ModToolException('Invalid module name.')
- self._dir = options.directory
- if self._dir == '.':
- self._dir = './gr-%s' % self._info['modname']
- try:
- os.stat(self._dir)
- except OSError:
- pass # This is what should happen
- else:
- raise ModToolException('The given directory exists.')
- if options.srcdir is None:
- options.srcdir = '/usr/local/share/gnuradio/modtool/gr-newmod'
- self._srcdir = gr.prefs().get_string('modtool', 'newmod_path', options.srcdir)
- if not os.path.isdir(self._srcdir):
- raise ModToolException('Could not find gr-newmod source dir.')
- self.options = options
- self._setup_scm(mode='new')
-
- def run(self, options):
- """
- * Copy the example dir recursively
- * Open all files, rename howto and HOWTO to the module name
- * Rename files and directories that contain the word howto
- """
- self.setup(options)
- print("Creating out-of-tree module in %s..." % (self._dir,))
- try:
- shutil.copytree(self._srcdir, self._dir)
- os.chdir(self._dir)
- except OSError:
- raise ModToolException('Could not create directory %s.' % self._dir)
- for root, dirs, files in os.walk('.'):
- for filename in files:
- f = os.path.join(root, filename)
- s = open(f, 'r').read()
- s = s.replace('howto', self._info['modname'])
- s = s.replace('HOWTO', self._info['modname'].upper())
- open(f, 'w').write(s)
- if filename.find('howto') != -1:
- os.rename(f, os.path.join(root, filename.replace('howto', self._info['modname'])))
- if os.path.basename(root) == 'howto':
- os.rename(root, os.path.join(os.path.dirname(root), self._info['modname']))
- print("Done.")
- if self.scm.init_repo(path_to_repo="."):
- print("Created repository... you might want to commit before continuing.")
- print("Use 'gr_modtool add' to add a new block to this currently empty module.")
-
diff --git a/gr-utils/python/modtool/templates/CMakeLists.txt b/gr-utils/python/modtool/templates/CMakeLists.txt
new file mode 100644
index 0000000000..9d69c5ce02
--- /dev/null
+++ b/gr-utils/python/modtool/templates/CMakeLists.txt
@@ -0,0 +1,31 @@
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+include(GrPython)
+
+GR_PYTHON_INSTALL(FILES
+ __init__.py
+ templates.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/templates
+)
+
+set(GR_PKG_MODTOOL_DATA_DIR ${GR_PKG_DATA_DIR}/modtool/templates)
+install(DIRECTORY gr-newmod
+ DESTINATION ${GR_PKG_MODTOOL_DATA_DIR}
+)
diff --git a/gr-utils/python/modtool/templates/__init__.py b/gr-utils/python/modtool/templates/__init__.py
new file mode 100644
index 0000000000..fcde85d69b
--- /dev/null
+++ b/gr-utils/python/modtool/templates/__init__.py
@@ -0,0 +1,26 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from .templates import Templates
diff --git a/gr-utils/python/modtool/gr-newmod/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/CMakeLists.txt
index e03d787828..e03d787828 100644
--- a/gr-utils/python/modtool/gr-newmod/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/CMakeLists.txt
diff --git a/gr-utils/python/modtool/gr-newmod/MANIFEST.md b/gr-utils/python/modtool/templates/gr-newmod/MANIFEST.md
index cafdae8213..cafdae8213 100644
--- a/gr-utils/python/modtool/gr-newmod/MANIFEST.md
+++ b/gr-utils/python/modtool/templates/gr-newmod/MANIFEST.md
diff --git a/gr-utils/python/modtool/gr-newmod/apps/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/apps/CMakeLists.txt
index e92732f65f..e92732f65f 100644
--- a/gr-utils/python/modtool/gr-newmod/apps/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/apps/CMakeLists.txt
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake
index 66016cb2ff..66016cb2ff 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/CMakeParseArgumentsCopy.cmake
diff --git a/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindCppUnit.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindCppUnit.cmake
new file mode 100644
index 0000000000..f93ade3412
--- /dev/null
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindCppUnit.cmake
@@ -0,0 +1,39 @@
+# http://www.cmake.org/pipermail/cmake/2006-October/011446.html
+# Modified to use pkg config and use standard var names
+
+#
+# Find the CppUnit includes and library
+#
+# This module defines
+# CPPUNIT_INCLUDE_DIR, where to find tiff.h, etc.
+# CPPUNIT_LIBRARIES, the libraries to link against to use CppUnit.
+# CPPUNIT_FOUND, If false, do not try to use CppUnit.
+
+INCLUDE(FindPkgConfig)
+PKG_CHECK_MODULES(PC_CPPUNIT "cppunit")
+
+FIND_PATH(CPPUNIT_INCLUDE_DIRS
+ NAMES cppunit/TestCase.h
+ HINTS ${PC_CPPUNIT_INCLUDE_DIR}
+ ${CMAKE_INSTALL_PREFIX}/include
+ PATHS
+ /usr/local/include
+ /usr/include
+)
+
+FIND_LIBRARY(CPPUNIT_LIBRARIES
+ NAMES cppunit
+ HINTS ${PC_CPPUNIT_LIBDIR}
+ ${CMAKE_INSTALL_PREFIX}/lib
+ ${CMAKE_INSTALL_PREFIX}/lib64
+ PATHS
+ ${CPPUNIT_INCLUDE_DIRS}/../lib
+ /usr/local/lib
+ /usr/lib
+)
+
+LIST(APPEND CPPUNIT_LIBRARIES ${CMAKE_DL_LIBS})
+
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(CPPUNIT DEFAULT_MSG CPPUNIT_LIBRARIES CPPUNIT_INCLUDE_DIRS)
+MARK_AS_ADVANCED(CPPUNIT_LIBRARIES CPPUNIT_INCLUDE_DIRS)
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake
index 86044e52a3..86044e52a3 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/FindGnuradioRuntime.cmake
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrMiscUtils.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrMiscUtils.cmake
index 6b7bbc9d20..6b7bbc9d20 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrMiscUtils.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrMiscUtils.cmake
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPlatform.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPlatform.cmake
index ed9fc82580..ed9fc82580 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPlatform.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPlatform.cmake
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPython.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPython.cmake
index 588236e8b8..588236e8b8 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrPython.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrPython.cmake
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrSwig.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrSwig.cmake
index bd9fc8cb62..bd9fc8cb62 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrSwig.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrSwig.cmake
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrTest.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrTest.cmake
index eee601a791..eee601a791 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/GrTest.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/GrTest.cmake
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/UseSWIG.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/UseSWIG.cmake
index a37cc41ae4..a37cc41ae4 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/UseSWIG.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/UseSWIG.cmake
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/Modules/howtoConfig.cmake b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/howtoConfig.cmake
index 88fbcad238..88fbcad238 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/Modules/howtoConfig.cmake
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/Modules/howtoConfig.cmake
diff --git a/gr-utils/python/modtool/gr-newmod/cmake/cmake_uninstall.cmake.in b/gr-utils/python/modtool/templates/gr-newmod/cmake/cmake_uninstall.cmake.in
index 9ae1ae4bd6..9ae1ae4bd6 100644
--- a/gr-utils/python/modtool/gr-newmod/cmake/cmake_uninstall.cmake.in
+++ b/gr-utils/python/modtool/templates/gr-newmod/cmake/cmake_uninstall.cmake.in
diff --git a/gr-utils/python/modtool/gr-newmod/docs/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/docs/CMakeLists.txt
index 8c86a96fc2..8c86a96fc2 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/CMakeLists.txt
diff --git a/gr-utils/python/modtool/gr-newmod/docs/README.howto b/gr-utils/python/modtool/templates/gr-newmod/docs/README.howto
index b29ce00579..b29ce00579 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/README.howto
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/README.howto
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/CMakeLists.txt
index 7aa31fcecd..7aa31fcecd 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/CMakeLists.txt
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.in b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in
index cabbb34167..cabbb34167 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.in
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.in
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in
index 33bc2afd07..33bc2afd07 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/Doxyfile.swig_doc.in
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/__init__.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/__init__.py
index 22f4eb351d..22f4eb351d 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/__init__.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/__init__.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/base.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/base.py
index b21369221c..b21369221c 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/base.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/base.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/doxyindex.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/doxyindex.py
index 5401bf2666..5401bf2666 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/doxyindex.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/doxyindex.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py
index 23095c1f34..23095c1f34 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/__init__.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compound.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compound.py
index acfa0dd5c6..acfa0dd5c6 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compound.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compound.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py
index 6e984e13ec..6e984e13ec 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/compoundsuper.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/index.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/index.py
index 0c63512119..0c63512119 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/index.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/index.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py
index 11312db635..11312db635 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/generated/indexsuper.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/text.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/text.py
index caf0b05480..caf0b05480 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/doxyxml/text.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/doxyxml/text.py
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/other/group_defs.dox b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/group_defs.dox
index 708f8c6d98..708f8c6d98 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/other/group_defs.dox
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/group_defs.dox
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/other/main_page.dox b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/main_page.dox
index 6357044912..6357044912 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/other/main_page.dox
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/other/main_page.dox
diff --git a/gr-utils/python/modtool/gr-newmod/docs/doxygen/swig_doc.py b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/swig_doc.py
index 07c5b75401..07c5b75401 100644
--- a/gr-utils/python/modtool/gr-newmod/docs/doxygen/swig_doc.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/docs/doxygen/swig_doc.py
diff --git a/gr-utils/python/modtool/gr-newmod/examples/README b/gr-utils/python/modtool/templates/gr-newmod/examples/README
index c012bdfa0a..c012bdfa0a 100644
--- a/gr-utils/python/modtool/gr-newmod/examples/README
+++ b/gr-utils/python/modtool/templates/gr-newmod/examples/README
diff --git a/gr-utils/python/modtool/gr-newmod/grc/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/grc/CMakeLists.txt
index b0ce5e79a9..b0ce5e79a9 100644
--- a/gr-utils/python/modtool/gr-newmod/grc/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/grc/CMakeLists.txt
diff --git a/gr-utils/python/modtool/gr-newmod/include/howto/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/include/howto/CMakeLists.txt
index 664ca26a34..664ca26a34 100644
--- a/gr-utils/python/modtool/gr-newmod/include/howto/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/include/howto/CMakeLists.txt
diff --git a/gr-utils/python/modtool/gr-newmod/include/howto/api.h b/gr-utils/python/modtool/templates/gr-newmod/include/howto/api.h
index 03620be2df..03620be2df 100644
--- a/gr-utils/python/modtool/gr-newmod/include/howto/api.h
+++ b/gr-utils/python/modtool/templates/gr-newmod/include/howto/api.h
diff --git a/gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/lib/CMakeLists.txt
index 2460b930f6..2460b930f6 100644
--- a/gr-utils/python/modtool/gr-newmod/lib/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/lib/CMakeLists.txt
diff --git a/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.cc b/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.cc
new file mode 100644
index 0000000000..2f10a3a564
--- /dev/null
+++ b/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.cc
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 Free Software Foundation, Inc.
+ *
+ * This file was generated by gr_modtool, a tool from the GNU Radio framework
+ * This file is a part of gr-howto
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * This class gathers together all the test cases for the gr-filter
+ * directory into a single test suite. As you create new test cases,
+ * add them here.
+ */
+
+#include "qa_howto.h"
+
+CppUnit::TestSuite *
+qa_howto::suite()
+{
+ CppUnit::TestSuite *s = new CppUnit::TestSuite("howto");
+
+ return s;
+}
diff --git a/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.h b/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.h
new file mode 100644
index 0000000000..c50bde3ec7
--- /dev/null
+++ b/gr-utils/python/modtool/templates/gr-newmod/lib/qa_howto.h
@@ -0,0 +1,39 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2012 Free Software Foundation, Inc.
+ *
+ * This file was generated by gr_modtool, a tool from the GNU Radio framework
+ * This file is a part of gr-howto
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _QA_HOWTO_H_
+#define _QA_HOWTO_H_
+
+#include <gnuradio/attributes.h>
+#include <cppunit/TestSuite.h>
+
+//! collect all the tests for the gr-filter directory
+
+class __GR_ATTR_EXPORT qa_howto
+{
+ public:
+ //! return suite of tests for all of gr-filter directory
+ static CppUnit::TestSuite *suite();
+};
+
+#endif /* _QA_HOWTO_H_ */
diff --git a/gr-utils/python/modtool/templates/gr-newmod/lib/test_howto.cc b/gr-utils/python/modtool/templates/gr-newmod/lib/test_howto.cc
new file mode 100644
index 0000000000..699bb5b640
--- /dev/null
+++ b/gr-utils/python/modtool/templates/gr-newmod/lib/test_howto.cc
@@ -0,0 +1,49 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2012 Free Software Foundation, Inc.
+ *
+ * This file was generated by gr_modtool, a tool from the GNU Radio framework
+ * This file is a part of gr-howto
+ *
+ * GNU Radio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * GNU Radio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU Radio; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <cppunit/TextTestRunner.h>
+#include <cppunit/XmlOutputter.h>
+
+#include <gnuradio/unittests.h>
+#include "qa_howto.h"
+#include <iostream>
+#include <fstream>
+
+int
+main (int argc, char **argv)
+{
+ CppUnit::TextTestRunner runner;
+ std::ofstream xmlfile(get_unittest_path("howto.xml").c_str());
+ CppUnit::XmlOutputter *xmlout = new CppUnit::XmlOutputter(&runner.result(), xmlfile);
+
+ runner.addTest(qa_howto::suite());
+ runner.setOutputter(xmlout);
+
+ bool was_successful = runner.run("", false);
+
+ return was_successful ? 0 : 1;
+}
diff --git a/gr-utils/python/modtool/gr-newmod/python/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/python/CMakeLists.txt
index 4b5303cc6f..4b5303cc6f 100644
--- a/gr-utils/python/modtool/gr-newmod/python/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/python/CMakeLists.txt
diff --git a/gr-utils/python/modtool/gr-newmod/python/__init__.py b/gr-utils/python/modtool/templates/gr-newmod/python/__init__.py
index 806f287cc3..06b4f6057a 100644
--- a/gr-utils/python/modtool/gr-newmod/python/__init__.py
+++ b/gr-utils/python/modtool/templates/gr-newmod/python/__init__.py
@@ -26,10 +26,10 @@ from __future__ import unicode_literals
# import swig generated symbols into the howto namespace
try:
- # this might fail if the module is python-only
- from howto_swig import *
+ # this might fail if the module is python-only
+ from .howto_swig import *
except ImportError:
- pass
+ pass
# import any pure python here
#
diff --git a/gr-utils/python/modtool/gr-newmod/swig/CMakeLists.txt b/gr-utils/python/modtool/templates/gr-newmod/swig/CMakeLists.txt
index bc11347447..bc11347447 100644
--- a/gr-utils/python/modtool/gr-newmod/swig/CMakeLists.txt
+++ b/gr-utils/python/modtool/templates/gr-newmod/swig/CMakeLists.txt
diff --git a/gr-utils/python/modtool/gr-newmod/swig/howto_swig.i b/gr-utils/python/modtool/templates/gr-newmod/swig/howto_swig.i
index 4627d48d34..ea05727d94 100644
--- a/gr-utils/python/modtool/gr-newmod/swig/howto_swig.i
+++ b/gr-utils/python/modtool/templates/gr-newmod/swig/howto_swig.i
@@ -2,7 +2,7 @@
#define HOWTO_API
-%include "gnuradio.i" // the common stuff
+%include "gnuradio.i" // the common stuff
//load generated python docstrings
%include "howto_swig_doc.i"
diff --git a/gr-utils/python/modtool/templates.py b/gr-utils/python/modtool/templates/templates.py
index 1ba7732d7c..f051c3d0f9 100644
--- a/gr-utils/python/modtool/templates.py
+++ b/gr-utils/python/modtool/templates/templates.py
@@ -1,5 +1,5 @@
#
-# Copyright 2013-2014,2018 Free Software Foundation, Inc.
+# Copyright 2013-2014, 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -19,6 +19,9 @@
# Boston, MA 02110-1301, USA.
#
''' All the templates for skeleton files (needed by ModToolAdd) '''
+
+from __future__ import print_function
+from __future__ import absolute_import
from __future__ import unicode_literals
from datetime import datetime
@@ -396,16 +399,16 @@ class ${blockname}(${parenttype}):
${parenttype}.__init__(self,
% if blocktype == 'hier':
"${blockname}",
- gr.io_signature(${inputsig}), # Input signature
- gr.io_signature(${outputsig})) # Output signature
+ "gr.io_signature(${inputsig})", # Input signature
+ "gr.io_signature(${outputsig})") # Output signature
# Define blocks and connect them
self.connect()
<% return %>
% else:
name="${blockname}",
- in_sig=${inputsig},
- out_sig=${outputsig}${deciminterp})
+ in_sig="${inputsig}",
+ out_sig="${outputsig}${deciminterp}")
% endif
% if blocktype == 'general':
@@ -416,8 +419,8 @@ class ${blockname}(${parenttype}):
def general_work(self, input_items, output_items):
output_items[0][:] = input_items[0]
- consume(0, len(input_items[0]))
- \\#self.consume_each(len(input_items[0]))
+ consume(0, len(input_items[0]))\
+ #self.consume_each(len(input_items[0]))
return len(output_items[0])
<% return %>
% endif
@@ -524,13 +527,13 @@ ${str_to_python_comment(license)}
from gnuradio import gr, gr_unittest
from gnuradio import blocks
% if lang == 'cpp':
-import ${modname}.${modname}_swig as ${modname}
+from . import ${modname}_swig as ${modname}
% else:
-from ${blockname} import ${blockname}
+from .${blockname} import ${blockname}
% endif
-
class qa_${blockname}(gr_unittest.TestCase):
+
def setUp(self):
self.tb = gr.top_block()
@@ -544,47 +547,52 @@ class qa_${blockname}(gr_unittest.TestCase):
if __name__ == '__main__':
- gr_unittest.run(qa_${blockname}, "qa_${blockname}.xml")
+ gr_unittest.run(qa_${blockname}, "qa_${blockname}.yml")
'''
-Templates['grc_xml'] = r'''<?xml version="1.0"?>
-<block>
- <name>${blockname}</name>
- <key>${modname}_${blockname}</key>
- <category>[${modname}]</category>
- <import>import ${modname}</import>
- <make>${modname}.${blockname}(${strip_arg_types_grc(arglist)})</make>
- <!-- Make one 'param' node for every Parameter you want settable from the GUI.
- Sub-nodes:
- * name
- * key (makes the value accessible as \$keyname, e.g. in the make node)
- * type -->
- <param>
- <name>...</name>
- <key>...</key>
- <type>...</type>
- </param>
-
- <!-- Make one 'sink' node per input. Sub-nodes:
- * name (an identifier for the GUI)
- * type
- * vlen
- * optional (set to 1 for optional inputs) -->
- <sink>
- <name>in</name>
- <type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
- </sink>
-
- <!-- Make one 'source' node per output. Sub-nodes:
- * name (an identifier for the GUI)
- * type
- * vlen
- * optional (set to 1 for optional inputs) -->
- <source>
- <name>out</name>
- <type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
- </source>
-</block>
+Templates['grc_yml'] = '''id: ${modname}_${blockname}
+label: ${blockname}
+category: [${modname}]
+
+templates:
+ imports: import ${modname}
+ make: ${modname}.${blockname}(${strip_arg_types_grc(arglist)})
+
+<!-- Make one 'parameters' list entry for every Parameter you want settable from the GUI.
+ Sub-entries of dictionary:
+ * id (makes the value accessible as \$keyname, e.g. in the make entry)
+ * label
+ * dtype -->
+parameters:
+- id: ...
+ label: ...
+ dtype: ...
+- id: ...
+ label: ...
+ dtype: ...
+
+<!-- Make one 'inputs' list entry per input. Sub-entries of dictionary:
+ * label (an identifier for the GUI)
+ * domain
+ * dtype
+ * vlen
+ * optional (set to 1 for optional inputs) -->
+inputs:
+- label: ...
+ domain: ...
+ dtype: ...
+ vlen: ...
+
+<!-- Make one 'outputs' list entry per output. Sub-entries of dictionary:
+ * label (an identifier for the GUI)
+ * dtype
+ * vlen
+ * optional (set to 1 for optional inputs) -->
+- label: ...
+ domain: ...
+ dtype: !-- e.g. int, float, complex, byte, short, xxx_vector, ...--
+
+file_format: 1
'''
# SWIG string
diff --git a/gr-utils/python/modtool/tests/CMakeLists.txt b/gr-utils/python/modtool/tests/CMakeLists.txt
new file mode 100644
index 0000000000..beb77cba15
--- /dev/null
+++ b/gr-utils/python/modtool/tests/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+include(GrPython)
+
+GR_PYTHON_INSTALL(FILES
+ __init__.py
+ test_modtool.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/tests
+)
+
diff --git a/gr-utils/python/modtool/tests/__init__.py b/gr-utils/python/modtool/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/gr-utils/python/modtool/tests/__init__.py
diff --git a/gr-utils/python/modtool/tests/test_modtool.py b/gr-utils/python/modtool/tests/test_modtool.py
new file mode 100644
index 0000000000..111c88a7af
--- /dev/null
+++ b/gr-utils/python/modtool/tests/test_modtool.py
@@ -0,0 +1,294 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" The file for testing the gr-modtool scripts """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import shutil
+import tempfile
+import unittest
+import warnings
+from os import path
+from pylint.epylint import py_run
+
+from gnuradio.modtool.core import *
+
+class TestModToolCore(unittest.TestCase):
+ """ The tests for the modtool core """
+ def __init__(self, *args, **kwargs):
+ super(TestModToolCore, self).__init__(*args, **kwargs)
+ self.f_add = False
+ self.f_newmod = False
+
+ @classmethod
+ def setUpClass(cls):
+ """ create a temporary directory """
+ cls.test_dir = tempfile.mkdtemp()
+
+ @classmethod
+ def tearDownClass(cls):
+ """ remove the directory after the test """
+ shutil.rmtree(cls.test_dir)
+
+ def setUp(self):
+ """ create a new module and block before every test """
+ try:
+ warnings.simplefilter("ignore", ResourceWarning)
+ args = {'module_name':'howto', 'directory': self.test_dir}
+ ModToolNewModule(**args).run()
+ except (TypeError, ModToolException):
+ self.f_newmod = True
+ else:
+ try:
+ args = {'blockname':'square_ff', 'block_type':'general',
+ 'lang':'cpp', 'directory': self.test_dir + '/gr-howto',
+ 'add_python_qa': True}
+ ModToolAdd(**args).run()
+ except (TypeError, ModToolException):
+ self.f_add = True
+
+ def tearDown(self):
+ """ removes the created module """
+ # Required, else the new-module directory command
+ # in setup will throw exception after first test
+ ## cannot remove if directory is not created
+ if not self.f_newmod:
+ rmdir = self.test_dir + '/gr-howto'
+ shutil.rmtree(rmdir)
+
+ def test_newmod(self):
+ """ Tests for the API function newmod """
+ ## Tests for proper exceptions ##
+ test_dict = {}
+ test_dict['directory'] = self.test_dir
+ # module name not specified
+ self.assertRaises(ModToolException, ModToolNewModule(**test_dict).run)
+ test_dict['module_name'] = 'howto'
+ # expected module_name as a string instead of dict
+ self.assertRaises(TypeError, ModToolNewModule(test_dict).run)
+ # directory already exists
+ # will not be raised if the command in setup failed
+ self.assertRaises(ModToolException, ModToolNewModule(**test_dict).run)
+
+ ## Some tests for checking the created directory, sub-directories and files ##
+ test_dict['module_name'] = 'test'
+ ModToolNewModule(**test_dict).run()
+ module_dir = path.join(self.test_dir, 'gr-test')
+ self.assertTrue(path.isdir(module_dir))
+ self.assertTrue(path.isdir(path.join(module_dir, 'lib')))
+ self.assertTrue(path.isdir(path.join(module_dir, 'python')))
+ self.assertTrue(path.isdir(path.join(module_dir, 'include')))
+ self.assertTrue(path.isdir(path.join(module_dir, 'docs')))
+ self.assertTrue(path.isdir(path.join(module_dir, 'cmake')))
+ self.assertTrue(path.isdir(path.join(module_dir, 'swig')))
+ self.assertTrue(path.exists(path.join(module_dir, 'CMakeLists.txt')))
+
+ ## pylint tests ##
+ python_dir = path.join(module_dir, 'python')
+ py_module = path.join(python_dir, 'build_utils.py')
+ (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True)
+ print(pylint_stdout.getvalue(), end='')
+ py_module = path.join(python_dir, 'build_utils_codes.py')
+ (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True)
+ print(pylint_stdout.getvalue(), end='')
+
+ ## The check for object instantiation ##
+ test_obj = ModToolNewModule()
+ # module name not specified
+ self.assertRaises(ModToolException, test_obj.run)
+ test_obj.info['modname'] = 'howto'
+ test_obj.directory = self.test_dir
+ # directory already exists
+ self.assertRaises(ModToolException, test_obj.run)
+ test_obj.info['modname'] = 'test1'
+ test_obj.run()
+ self.assertTrue(path.isdir(self.test_dir+'/gr-test1'))
+ self.assertTrue(path.isdir(self.test_dir+'/gr-test1/lib'))
+ self.assertTrue(path.exists(self.test_dir+'/gr-test1/CMakeLists.txt'))
+
+ def test_add(self):
+ """ Tests for the API function add """
+ ## skip tests if newmod command wasn't successful
+ if self.f_newmod:
+ raise unittest.SkipTest("setUp for API function 'add' failed")
+ module_dir = path.join(self.test_dir, 'gr-howto')
+ ## Tests for proper exceptions ##
+ test_dict = {}
+ test_dict['directory'] = module_dir
+ # missing blockname, block_type, lang
+ self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
+ test_dict['blockname'] = 'add_ff'
+ # missing arguments block_type, lang
+ self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
+ test_dict['block_type'] = 'general'
+ # missing argument lang
+ self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
+ test_dict['lang'] = 'cxx'
+ # incorrect language
+ self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
+ test_dict['lang'] = 'cpp'
+ test_dict['add_cpp_qa'] = 'Wrong'
+ # boolean is expected for add_cpp_qa
+ self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
+ test_dict['add_cpp_qa'] = True
+ test_dict['block_type'] = 'generaleee'
+ # incorrect block type
+ self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
+ test_dict['block_type'] = 'general'
+ test_dict['skip_lib'] = 'fail'
+ # boolean value is expected for skip_lib, fails in instantiation
+ self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
+ test_dict['skip_lib'] = True
+ # missing relevant subdir
+ self.assertRaises(ModToolException, ModToolAdd(**test_dict).run)
+
+ ## Some tests for checking the created directory, sub-directories and files ##
+ test_dict['skip_lib'] = False
+ ModToolAdd(**test_dict).run()
+ self.assertTrue(path.exists(path.join(module_dir, 'lib', 'qa_add_ff.cc')))
+ self.assertTrue(path.exists(path.join(module_dir, 'lib', 'add_ff_impl.cc')))
+ self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_add_ff.block.yml')))
+ self.assertTrue(path.exists(path.join(module_dir, 'include', 'howto', 'add_ff.h')))
+
+ ## The check for object instantiation ##
+ test_obj = ModToolAdd()
+ test_obj.dir = module_dir
+ # missing blocktype, lang, blockname
+ self.assertRaises(ModToolException, test_obj.run)
+ test_obj.info['blocktype'] = 'general'
+ # missing lang, blockname
+ self.assertRaises(ModToolException, test_obj.run)
+ test_obj.info['lang'] = 'python'
+ test_obj.info['blockname'] = 'mul_ff'
+ test_obj.add_py_qa = True
+ test_obj.run()
+ self.assertTrue(path.exists(path.join(module_dir, 'python', 'mul_ff.py')))
+ self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_mul_ff.py')))
+ self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_mul_ff.block.yml')))
+
+ ## pylint tests ##
+ python_dir = path.join(module_dir, 'python')
+ py_module = path.join(python_dir, 'mul_ff.py')
+ (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only --disable=E0602', return_std=True)
+ print(pylint_stdout.getvalue(), end='')
+ py_module = path.join(python_dir, 'qa_mul_ff.py')
+ (pylint_stdout, pylint_stderr) = py_run(py_module+' --errors-only', return_std=True)
+ print(pylint_stdout.getvalue(), end='')
+
+ def test_rename(self):
+ """ Tests for the API function rename """
+ if self.f_newmod or self.f_add:
+ raise unittest.SkipTest("setUp for API function 'rename' failed")
+
+ module_dir = path.join(self.test_dir, 'gr-howto')
+ test_dict = {}
+ test_dict['directory'] = module_dir
+ # Missing 2 arguments blockname, new_name
+ self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
+ test_dict['blockname'] = 'square_ff'
+ # Missing argument new_name
+ self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
+ test_dict['new_name'] = '//#'
+ # Invalid new block name!
+ self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
+ test_dict['new_name'] = None
+ # New Block name not specified
+ self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
+
+ ## Some tests for checking the renamed files ##
+ test_dict['new_name'] = 'div_ff'
+ ModToolRename(**test_dict).run()
+ self.assertTrue(path.exists(path.join(module_dir, 'lib', 'div_ff_impl.h')))
+ self.assertTrue(path.exists(path.join(module_dir, 'lib', 'div_ff_impl.cc')))
+ self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_div_ff.py')))
+ self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_div_ff.block.yml')))
+
+ ## The check for object instantiation ##
+ test_obj = ModToolRename()
+ test_obj.info['oldname'] = 'div_ff'
+ test_obj.info['newname'] = 'sub_ff'
+ test_obj.run()
+ self.assertTrue(path.exists(path.join(module_dir, 'lib', 'sub_ff_impl.h')))
+ self.assertTrue(path.exists(path.join(module_dir, 'lib', 'sub_ff_impl.cc')))
+ self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_sub_ff.py')))
+ self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_sub_ff.block.yml')))
+
+ def test_remove(self):
+ """ Tests for the API function remove """
+ if self.f_newmod or self.f_add:
+ raise unittest.SkipTest("setUp for API function 'remove' failed")
+ module_dir = path.join(self.test_dir, 'gr-howto')
+ test_dict = {}
+ # missing argument blockname
+ self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
+ test_dict['directory'] = module_dir
+ self.assertRaises(ModToolException, ModToolRename(**test_dict).run)
+
+ ## Some tests to check blocks are not removed with different blocknames ##
+ test_dict['blockname'] = 'div_ff'
+ ModToolRemove(**test_dict).run()
+ self.assertTrue(path.exists(path.join(module_dir, 'lib', 'square_ff_impl.h')))
+ self.assertTrue(path.exists(path.join(module_dir, 'lib', 'square_ff_impl.cc')))
+ self.assertTrue(path.exists(path.join(module_dir, 'python', 'qa_square_ff.py')))
+ self.assertTrue(path.exists(path.join(module_dir, 'grc', 'howto_square_ff.block.yml')))
+
+ ## Some tests for checking the non-existence of removed files ##
+ test_dict['blockname'] = 'square_ff'
+ ModToolRemove(**test_dict).run()
+ self.assertTrue(not path.exists(path.join(module_dir, 'lib', 'square_ff_impl.h')))
+ self.assertTrue(not path.exists(path.join(module_dir, 'lib', 'square_ff_impl.cc')))
+ self.assertTrue(not path.exists(path.join(module_dir, 'python', 'qa_square_ff.py')))
+ self.assertTrue(not path.exists(path.join(module_dir, 'grc', 'howto_square_ff.block.yml')))
+
+ def test_makeyaml(self):
+ """ Tests for the API function makeyaml """
+ if self.f_newmod or self.f_add:
+ raise unittest.SkipTest("setUp for API function 'makeyaml' failed")
+ module_dir = path.join(self.test_dir, 'gr-howto')
+ test_dict = {}
+ # missing argument blockname
+ self.assertRaises(ModToolException, ModToolMakeYAML(**test_dict).run)
+ test_dict['directory'] = module_dir
+ self.assertRaises(ModToolException, ModToolMakeYAML(**test_dict).run)
+
+ ## Some tests to check if the command reuns ##
+ test_dict['blockname'] = 'square_ff'
+ ModToolMakeYAML(**test_dict).run()
+
+ def test_disable(self):
+ """ Tests for the API function disable """
+ if self.f_newmod or self.f_add:
+ raise unittest.SkipTest("setUp for API function 'disable' failed")
+ module_dir = path.join(self.test_dir, 'gr-howto')
+ test_dict = {}
+ # missing argument blockname
+ self.assertRaises(ModToolException, ModToolDisable(**test_dict).run)
+ test_dict['directory'] = module_dir
+ self.assertRaises(ModToolException, ModToolDisable(**test_dict).run)
+
+ ## Some tests to check if the command reuns ##
+ test_dict['blockname'] = 'square_ff'
+ ModToolDisable(**test_dict).run()
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/gr-utils/python/modtool/tools/CMakeLists.txt b/gr-utils/python/modtool/tools/CMakeLists.txt
new file mode 100644
index 0000000000..f9acf9bfe7
--- /dev/null
+++ b/gr-utils/python/modtool/tools/CMakeLists.txt
@@ -0,0 +1,31 @@
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+
+include(GrPython)
+
+GR_PYTHON_INSTALL(FILES
+ __init__.py
+ cmakefile_editor.py
+ code_generator.py
+ grc_yaml_generator.py
+ parser_cc_block.py
+ scm.py
+ util_functions.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool/tools
+)
diff --git a/gr-utils/python/modtool/tools/__init__.py b/gr-utils/python/modtool/tools/__init__.py
new file mode 100644
index 0000000000..11cff70d59
--- /dev/null
+++ b/gr-utils/python/modtool/tools/__init__.py
@@ -0,0 +1,31 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from .cmakefile_editor import CMakeFileEditor
+from .code_generator import render_template
+from .grc_yaml_generator import GRCYAMLGenerator
+from .parser_cc_block import ParserCCBlock
+from .scm import SCMRepoFactory
+from .util_functions import * \ No newline at end of file
diff --git a/gr-utils/python/modtool/cmakefile_editor.py b/gr-utils/python/modtool/tools/cmakefile_editor.py
index 40d88fac18..0e3ede0be9 100644
--- a/gr-utils/python/modtool/cmakefile_editor.py
+++ b/gr-utils/python/modtool/tools/cmakefile_editor.py
@@ -1,5 +1,5 @@
#
-# Copyright 2013 Free Software Foundation, Inc.
+# Copyright 2013, 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -21,21 +21,27 @@
""" Edit CMakeLists.txt files """
from __future__ import print_function
+from __future__ import absolute_import
from __future__ import unicode_literals
import re
+import logging
+
+logger = logging.getLogger(__name__)
+
class CMakeFileEditor(object):
"""A tool for editing CMakeLists.txt files. """
def __init__(self, filename, separator='\n ', indent=' '):
self.filename = filename
- self.cfile = open(filename, 'r').read()
+ 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[^()]*?)\s*?(\s?%s)\)' % (entry, to_ignore_start, to_ignore_end),
+ 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)
@@ -80,14 +86,15 @@ class CMakeFileEditor(object):
def delete_entry(self, entry, value_pattern=''):
"""Remove an entry from the current buffer."""
- regexp = r'%s\s*\([^()]*%s[^()]*\)[^\n]*\n' % (entry, value_pattern)
+ 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. """
- open(self.filename, 'w').write(self.cfile)
+ with open(self.filename, 'w') as f:
+ f.write(self.cfile)
def remove_double_newlines(self):
"""Simply clear double newlines from the file buffer."""
@@ -129,9 +136,9 @@ class CMakeFileEditor(object):
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:
- print("Warning: A replacement failed when commenting out %s. Check the CMakeFile.txt manually." % fname)
+ logger.warning("Warning: A replacement failed when commenting out {}. Check the CMakeFile.txt manually.".format(fname))
elif nsubs > 1:
- print("Warning: Replaced %s %d times (instead of once). Check the CMakeFile.txt manually." % (fname, nsubs))
+ 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 """
@@ -141,5 +148,5 @@ class CMakeFileEditor(object):
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"%s"' % globstr.replace('*', r'\*')
+ 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
diff --git a/gr-utils/python/modtool/code_generator.py b/gr-utils/python/modtool/tools/code_generator.py
index d95434f577..eccecf895b 100644
--- a/gr-utils/python/modtool/code_generator.py
+++ b/gr-utils/python/modtool/tools/code_generator.py
@@ -19,11 +19,13 @@
# Boston, MA 02110-1301, USA.
#
""" A code generator (needed by ModToolAdd) """
+
+from __future__ import print_function
from __future__ import absolute_import
from __future__ import unicode_literals
from mako.template import Template
-from .templates import Templates
+from ..templates import Templates
from .util_functions import str_to_fancyc_comment
from .util_functions import str_to_python_comment
from .util_functions import strip_default_values
diff --git a/gr-utils/python/modtool/tools/grc_yaml_generator.py b/gr-utils/python/modtool/tools/grc_yaml_generator.py
new file mode 100644
index 0000000000..ce61f46069
--- /dev/null
+++ b/gr-utils/python/modtool/tools/grc_yaml_generator.py
@@ -0,0 +1,138 @@
+#
+# Copyright 2018 Free Software Foundation, Inc.
+#
+# This file is part of GNU Radio
+#
+# GNU Radio is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GNU Radio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GNU Radio; see the file COPYING. If not, write to
+# the Free Software Foundation, Inc., 51 Franklin Street,
+# Boston, MA 02110-1301, USA.
+#
+""" A tool for generating YAML bindings """
+
+from __future__ import print_function
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from collections import OrderedDict
+
+import yaml
+from yaml import CLoader as Loader, CDumper as Dumper
+
+from .util_functions import is_number
+
+
+## setup dumper for dumping OrderedDict ##
+_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
+
+def dict_representer(dumper, data):
+ """ Representer to represent special OrderedDict """
+ return dumper.represent_dict(data.items())
+
+
+def dict_constructor(loader, node):
+ """ Construct an OrderedDict for dumping """
+ return OrderedDict(loader.construct_pairs(node))
+
+Dumper.add_representer(OrderedDict, dict_representer)
+Loader.add_constructor(_mapping_tag, dict_constructor)
+
+
+class GRCYAMLGenerator(object):
+ """ Create and write the YAML bindings for a GRC block. """
+ def __init__(self, modname=None, blockname=None, doc=None, params=None, iosig=None):
+ """docstring for __init__"""
+ params_list = ['$'+s['key'] for s in params if s['in_constructor']]
+ # Can't make a dict 'cause order matters
+ self._header = (('id', '{}_{}'.format(modname, blockname)),
+ ('label', blockname.replace('_', ' ').capitalize()),
+ ('category', '[{}]'.format(modname.upper()))
+ )
+ self._templates = (('imports', 'import {}'.format(modname)),
+ ('make', '{}.{}({})'.format(modname, blockname, ', '.join(params_list)))
+ )
+ self.params = params
+ self.iosig = iosig
+ self.doc = doc
+ self.data = None
+
+ def make_yaml(self):
+ """ Create the actual tag tree """
+ data = OrderedDict()
+ for tag, value in self._header:
+ data[tag] = value
+
+ templates = OrderedDict()
+ for tag, value in self._templates:
+ templates[tag] = value
+
+ data['templates'] = templates
+
+ parameters = []
+ for param in self.params:
+ parameter = OrderedDict()
+ parameter['id'] = param['key']
+ parameter['label'] = param['key'].capitalize()
+ if param['default']:
+ parameter['default'] = param['default']
+ parameter['dtype'] = param['type']
+ parameters.append(parameter)
+
+ if parameters:
+ data['parameters'] = parameters
+
+ inputs = []
+ outputs = []
+ iosig = self.iosig
+ for inout in sorted(iosig.keys()):
+ if iosig[inout]['max_ports'] == '0':
+ continue
+ for i in range(len(iosig[inout]['type'])):
+ s_type = {'in': 'input', 'out': 'output'}[inout]
+ s_obj = OrderedDict()
+ s_obj['label'] = inout
+ s_obj['domain'] = 'stream'
+ s_obj['dtype'] = iosig[inout]['type'][i]
+ if iosig[inout]['vlen'][i] != '1':
+ vlen = iosig[inout]['vlen'][i]
+ if is_number(vlen):
+ s_obj['vlen'] = vlen
+ else:
+ s_obj['vlen'] = '${ '+vlen+' }'
+ if i == len(iosig[inout]['type'])-1:
+ if not is_number(iosig[inout]['max_ports']):
+ s_obj['multiplicity'] = iosig[inout]['max_ports']
+ elif len(iosig[inout]['type']) < int(iosig[inout]['max_ports']):
+ s_obj['multiplicity'] = str(int(iosig[inout]['max_ports']) -
+ len(iosig[inout]['type'])+1)
+ if s_type == 'input':
+ inputs.append(s_obj)
+ elif s_type == 'output':
+ outputs.append(s_obj)
+
+ if inputs:
+ data['inputs'] = inputs
+
+ if outputs:
+ data['outputs'] = outputs
+
+ if self.doc is not None:
+ data['documentation'] = self.doc
+ self.data = data
+ data['file_format'] = 1
+
+ def save(self, filename):
+ """ Write the YAML file """
+ self.make_yaml()
+ with open(filename, 'w') as f:
+ yaml.dump(self.data, f, Dumper=Dumper, default_flow_style=False)
diff --git a/gr-utils/python/modtool/parser_cc_block.py b/gr-utils/python/modtool/tools/parser_cc_block.py
index 766b063ccd..5ed185d87f 100644
--- a/gr-utils/python/modtool/parser_cc_block.py
+++ b/gr-utils/python/modtool/tools/parser_cc_block.py
@@ -1,5 +1,5 @@
#
-# Copyright 2013 Free Software Foundation, Inc.
+# Copyright 2013, 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -21,10 +21,14 @@
''' A parser for blocks written in C++ '''
from __future__ import print_function
+from __future__ import absolute_import
from __future__ import unicode_literals
import re
import sys
+import logging
+
+logger = logging.getLogger(__name__)
def dummy_translator(the_type, default_v=None):
""" Doesn't really translate. """
@@ -33,8 +37,10 @@ def dummy_translator(the_type, default_v=None):
class ParserCCBlock(object):
""" Class to read blocks written in C++ """
def __init__(self, filename_cc, filename_h, blockname, version, type_trans=dummy_translator):
- self.code_cc = open(filename_cc).read()
- self.code_h = open(filename_h).read()
+ with open(filename_cc) as f:
+ self.code_cc = f.read()
+ with open(filename_h) as f:
+ self.code_h = f.read()
self.blockname = blockname
self.type_trans = type_trans
self.version = version
@@ -46,7 +52,7 @@ class ParserCCBlock(object):
E.g., for sizeof(int), it will return 'int'.
Returns a list! """
if 'gr::io_signature::makev' in iosigcall:
- print('tbi')
+ logger.error('tbi')
raise ValueError
return {'type': [_typestr_to_iotype(x) for x in typestr.split(',')],
'vlen': [_typestr_to_vlen(x) for x in typestr.split(',')]
@@ -87,14 +93,14 @@ class ParserCCBlock(object):
iosig['in']['min_ports'] = iosig_match.group('inmin')
iosig['in']['max_ports'] = iosig_match.group('inmax')
except Exception:
- print("Error: Can't parse input signature.")
+ logger.error("Error: Can't parse input signature.")
try:
iosig['out'] = _figure_out_iotype_and_vlen(iosig_match.group('outcall'),
iosig_match.group('outtype'))
iosig['out']['min_ports'] = iosig_match.group('outmin')
iosig['out']['max_ports'] = iosig_match.group('outmax')
except Exception:
- print("Error: Can't parse output signature.")
+ logger.error("Error: Can't parse output signature.")
return iosig
@@ -127,8 +133,9 @@ class ParserCCBlock(object):
if parens_count == 0:
if read_state == 'type' and len(this_type):
raise ValueError(
- 'Found closing parentheses before finishing last argument (this is how far I got: %s)'
- % str(param_list)
+ 'Found closing parentheses before finishing '
+ 'last argument (this is how far I got: {})'.format \
+ (str(param_list))
)
if len(this_type):
param_list.append((this_type, this_name, this_defv))
@@ -169,16 +176,16 @@ class ParserCCBlock(object):
elif c[i] == '=':
if parens_count != 0:
raise ValueError(
- 'While parsing argument %d (%s): name finished but no closing parentheses.'
- % (len(param_list)+1, this_type + ' ' + this_name)
+ 'While parsing argument {} ({}): name finished but no closing parentheses.'.format \
+ (len(param_list)+1, this_type + ' ' + this_name)
)
read_state = 'defv'
i += 1
elif c[i] == ',':
if parens_count:
raise ValueError(
- 'While parsing argument %d (%s): name finished but no closing parentheses.'
- % (len(param_list)+1, this_type + ' ' + this_name)
+ 'While parsing argument {} ({}): name finished but no closing parentheses.'.format \
+ (len(param_list)+1, this_type + ' ' + this_name)
)
read_state = 'defv'
else:
@@ -195,8 +202,8 @@ class ParserCCBlock(object):
elif c[i] == ',':
if parens_count:
raise ValueError(
- 'While parsing argument %d (%s): default value finished but no closing parentheses.'
- % (len(param_list)+1, this_type + ' ' + this_name)
+ 'While parsing argument {} ({}): default value finished but no closing parentheses.'.format \
+ (len(param_list)+1, this_type + ' ' + this_name)
)
read_state = 'type'
param_list.append((this_type, this_name, this_defv))
@@ -217,7 +224,7 @@ class ParserCCBlock(object):
try:
params_list = _scan_param_list(make_match.end(0))
except ValueError as ve:
- print("Can't parse the argument list: ", ve.args[0])
+ logger.error("Can't parse the argument list: ", ve.args[0])
sys.exit(0)
params = []
for plist in params_list:
diff --git a/gr-utils/python/modtool/scm.py b/gr-utils/python/modtool/tools/scm.py
index 4b3d5116ac..249616ade3 100644
--- a/gr-utils/python/modtool/scm.py
+++ b/gr-utils/python/modtool/tools/scm.py
@@ -21,10 +21,14 @@
""" Class to handle source code management repositories. """
from __future__ import print_function
+from __future__ import absolute_import
from __future__ import unicode_literals
+import logging
import subprocess
+logger = logging.getLogger(__name__)
+
try:
import git
HAS_GITPYTHON = True
@@ -206,7 +210,7 @@ class SCMRepoFactory(object):
if issubclass(glbl, SCMRepository):
the_scm = glbl(self.path_to_repo)
if the_scm.is_active():
- print('Found SCM of type:', the_scm.handles_scm_type)
+ logger.info('Found SCM of type:', the_scm.handles_scm_type)
return the_scm
except (TypeError, AttributeError, InvalidSCMError):
pass
diff --git a/gr-utils/python/modtool/util_functions.py b/gr-utils/python/modtool/tools/util_functions.py
index edd98fde8f..15409eacc3 100644
--- a/gr-utils/python/modtool/util_functions.py
+++ b/gr-utils/python/modtool/tools/util_functions.py
@@ -20,6 +20,8 @@
#
""" Utility functions for gr_modtool """
+from __future__ import print_function
+from __future__ import absolute_import
from __future__ import unicode_literals
import re
@@ -32,20 +34,25 @@ def append_re_line_sequence(filename, linepattern, newline):
""" Detects the re 'linepattern' in the file. After its last occurrence,
paste 'newline'. If the pattern does not exist, append the new line
to the file. Then, write. """
- oldfile = open(filename, 'r').read()
+ with open(filename, 'r') as f:
+ oldfile = f.read()
lines = re.findall(linepattern, oldfile, flags=re.MULTILINE)
if len(lines) == 0:
- open(filename, 'a').write(newline)
+ with open(filename, 'a') as f:
+ f.write(newline)
return
last_line = lines[-1]
newfile = oldfile.replace(last_line, last_line + newline + '\n')
- open(filename, 'w').write(newfile)
+ with open(filename, 'w') as f:
+ f.write(newfile)
def remove_pattern_from_file(filename, pattern):
""" Remove all occurrences of a given pattern from a file. """
- oldfile = open(filename, 'r').read()
+ with open(filename, 'r') as f:
+ oldfile = f.read()
pattern = re.compile(pattern, re.MULTILINE)
- open(filename, 'w').write(pattern.sub('', oldfile))
+ with open(filename, 'w') as f:
+ f.write(pattern.sub('', oldfile))
def str_to_fancyc_comment(text):
""" Return a string as a C formatted comment. """
@@ -90,13 +97,15 @@ def get_modname():
""" Grep the current module's name from gnuradio.project or CMakeLists.txt """
modname_trans = {'howto-write-a-block': 'howto'}
try:
- prfile = open('gnuradio.project', 'r').read()
+ with open('gnuradio.project', 'r') as f:
+ prfile = f.read()
regexp = r'projectname\s*=\s*([a-zA-Z0-9-_]+)$'
return re.search(regexp, prfile, flags=re.MULTILINE).group(1).strip()
except IOError:
pass
# OK, there's no gnuradio.project. So, we need to guess.
- cmfile = open('CMakeLists.txt', 'r').read()
+ with open('CMakeLists.txt', 'r') as f:
+ cmfile = f.read()
regexp = r'(project\s*\(\s*|GR_REGISTER_COMPONENT\(")gr-(?P<modname>[a-zA-Z0-9-_]+)(\s*(CXX)?|" ENABLE)'
try:
modname = re.search(regexp, cmfile, flags=re.MULTILINE).group('modname').strip()
@@ -114,22 +123,6 @@ def is_number(s):
except ValueError:
return False
-def xml_indent(elem, level=0):
- """ Adds indents to XML for pretty printing """
- i = "\n" + level*" "
- if len(elem):
- if not elem.text or not elem.text.strip():
- elem.text = i + " "
- if not elem.tail or not elem.tail.strip():
- elem.tail = i
- for elem in elem:
- xml_indent(elem, level+1)
- if not elem.tail or not elem.tail.strip():
- elem.tail = i
- else:
- if level and (not elem.tail or not elem.tail.strip()):
- elem.tail = i
-
def ask_yes_no(question, default):
""" Asks a binary question. Returns True for yes, False for no.
default is given as a boolean. """
@@ -156,7 +149,7 @@ class SequenceCompleter(object):
if not text and state < len(self._seq):
return self._seq[state]
if not state:
- self._tmp_matches = filter(lambda candidate: candidate.startswith(text), self._seq)
+ self._tmp_matches = [candidate for candidate in self._seq if candidate.startswith(text)]
if state < len(self._tmp_matches):
return self._tmp_matches[state]
diff --git a/gr-utils/python/utils/gr_modtool b/gr-utils/python/utils/gr_modtool
index f959c91f66..4f2738901e 100755
--- a/gr-utils/python/utils/gr_modtool
+++ b/gr-utils/python/utils/gr_modtool
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright 2012 Free Software Foundation, Inc.
+# Copyright 2012, 2018 Free Software Foundation, Inc.
#
# This file is part of GNU Radio
#
@@ -21,39 +21,12 @@
#
""" A tool for editing GNU Radio out-of-tree modules. """
-from __future__ import print_function
-from gnuradio.modtool import *
+from gnuradio.modtool.cli.base import cli
-def setup_parser():
- modules = get_modtool_modules(ModTool.__subclasses__())
- parser = ModTool.get_parser()
- subparsers = parser.add_subparsers(title="Commands")
- epilog = []
- for module in modules:
- subparser = subparsers.add_parser(module.name,
- description=module.description)
- module.setup_parser(subparser)
- subparser.set_defaults(module=module)
- epilog.append(" {:<22}{}".format(module.name, module.description))
- parser.epilog = '\n'.join(epilog)
- return parser
-
-def main():
- """ Here we go. Parse command, choose class and run. """
- parser = setup_parser()
- args = parser.parse_args()
-
- try:
- args.module().run(args)
- except ModToolException as err:
- print(err, file=sys.stderr)
- exit(1)
-
if __name__ == '__main__':
try:
- main()
+ cli()
except KeyboardInterrupt:
pass
-