summaryrefslogtreecommitdiff
path: root/gr-utils
diff options
context:
space:
mode:
authorMartin Braun2013-01-24 19:33:03 +0100
committerMartin Braun2013-01-24 19:33:03 +0100
commita62f90d8cc96b9dea9289ad6e420d1c0b16f6c36 (patch)
tree963f16e442ad47a0cca5187d03f372edfa305dd5 /gr-utils
parent4435082f6167f0b5b519deaa1609572d596757dc (diff)
downloadgnuradio-a62f90d8cc96b9dea9289ad6e420d1c0b16f6c36.tar.gz
gnuradio-a62f90d8cc96b9dea9289ad6e420d1c0b16f6c36.tar.bz2
gnuradio-a62f90d8cc96b9dea9289ad6e420d1c0b16f6c36.zip
utils: added modtool
Diffstat (limited to 'gr-utils')
-rw-r--r--gr-utils/src/python/CMakeLists.txt4
-rwxr-xr-xgr-utils/src/python/create-gnuradio-out-of-tree-project69
-rwxr-xr-xgr-utils/src/python/gr_modtool61
-rw-r--r--gr-utils/src/python/modtool/CMakeLists.txt40
-rw-r--r--gr-utils/src/python/modtool/README.modtool8
-rw-r--r--gr-utils/src/python/modtool/__init__.py34
-rw-r--r--gr-utils/src/python/modtool/cmakefile_editor.py97
-rw-r--r--gr-utils/src/python/modtool/code_generator.py32
-rw-r--r--gr-utils/src/python/modtool/grc_xml_generator.py85
-rw-r--r--gr-utils/src/python/modtool/modtool_add.py296
-rw-r--r--gr-utils/src/python/modtool/modtool_base.py139
-rw-r--r--gr-utils/src/python/modtool/modtool_disable.py144
-rw-r--r--gr-utils/src/python/modtool/modtool_help.py66
-rw-r--r--gr-utils/src/python/modtool/modtool_info.py137
-rw-r--r--gr-utils/src/python/modtool/modtool_makexml.py151
-rw-r--r--gr-utils/src/python/modtool/modtool_newmod.py88
-rw-r--r--gr-utils/src/python/modtool/modtool_rm.py155
-rw-r--r--gr-utils/src/python/modtool/newmod_tarfile.py1034
-rw-r--r--gr-utils/src/python/modtool/parser_cc_block.py205
-rw-r--r--gr-utils/src/python/modtool/templates.py698
-rw-r--r--gr-utils/src/python/modtool/util_functions.py127
21 files changed, 3600 insertions, 70 deletions
diff --git a/gr-utils/src/python/CMakeLists.txt b/gr-utils/src/python/CMakeLists.txt
index d2af6b952..ef4e66d13 100644
--- a/gr-utils/src/python/CMakeLists.txt
+++ b/gr-utils/src/python/CMakeLists.txt
@@ -35,7 +35,7 @@ GR_PYTHON_INSTALL(
GR_PYTHON_INSTALL(
PROGRAMS
- create-gnuradio-out-of-tree-project
+ gr_modtool
gr_plot_char
gr_plot_const
gr_plot_fft
@@ -55,3 +55,5 @@ GR_PYTHON_INSTALL(
DESTINATION ${GR_RUNTIME_DIR}
COMPONENT "utils"
)
+
+add_subdirectory(modtool)
diff --git a/gr-utils/src/python/create-gnuradio-out-of-tree-project b/gr-utils/src/python/create-gnuradio-out-of-tree-project
deleted file mode 100755
index d5e32c92b..000000000
--- a/gr-utils/src/python/create-gnuradio-out-of-tree-project
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python
-
-from optparse import OptionParser
-import os
-import re
-import sys
-import subprocess
-
-tarball_url="http://gnuradio.org/releases/gnuradio/gr-howto-write-a-block-3.3git-651-g642252d8.tar.gz"
-
-def open_tarball(tarball_name=None):
- """Return a readable fileobject that references the tarball.
- If tarball_name is None, download from the net
- """
- if tarball_name:
- return open(tarball_name, 'r')
- p = subprocess.Popen(["wget", "-O", "-", tarball_url],
- close_fds=True, stdout=subprocess.PIPE)
- return p.stdout
-
-def extract_and_rename_files(tarball, module_name):
- # Requires GNU tar.
- tar_cmd = 'tar xz --strip-components=1 --transform="s/howto/%s/g"' % (module_name,)
- p = subprocess.Popen(tar_cmd, shell=True, stdin=tarball, close_fds=True)
- sts = os.waitpid(p.pid, 0)
- return sts == 0
-
-def main():
- usage = "%prog: [options] new_module_name"
- description="Create a prototype 'out of tree' GNU Radio project"
- parser = OptionParser(usage=usage, description=description)
- parser.add_option("-T", "--tarball", type="string", default=None,
- help="path to gr-howto-build-a-block gzipped tarball [default=<get from web>]")
- (options, args) = parser.parse_args()
- if len(args) != 1:
- parser.print_help()
- raise SystemExit,2
- module_name = args[0]
- if not re.match(r'[_a-z][_a-z0-9]*$', module_name):
- sys.stderr.write("module_name '%s' may only contain [a-z0-9_]\n" % (module_name))
- raise SystemExit, 1
-
- # Ensure there's no file or directory called <module_name>
- if os.path.exists(module_name):
- sys.stderr.write("A file or directory named '%s' already exists\n" % (module_name,))
- raise SystemExit, 1
-
- os.mkdir(module_name)
- os.chdir(module_name)
-
- ok = extract_and_rename_files(open_tarball(options.tarball), module_name)
-
- # rename file contents
- upper_module_name = module_name.upper()
- sed_cmd = 'sed -i -e "s/howto-write-a-block/%s/g" -e "s/howto/%s/g" -e "s/HOWTO/%s/g"' % (module_name, module_name, \
- upper_module_name)
- os.system('find . -type f -print0 | xargs -0 ' + sed_cmd)
-
- sys.stdout.write("""
-Project '%s' is now ready to build.
-
- $ ./bootstrap
- $ ./configure --prefix=/home/[user]/install
- $ (make && make check && make install) 2>&1 | tee make.log
-
-""" % (module_name,))
-
-if __name__ == '__main__':
- main()
diff --git a/gr-utils/src/python/gr_modtool b/gr-utils/src/python/gr_modtool
new file mode 100755
index 000000000..bc41d56f5
--- /dev/null
+++ b/gr-utils/src/python/gr_modtool
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 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 editing GNU Radio out-of-tree modules. """
+
+import sys
+from gnuradio.modtool import *
+
+def get_class_dict():
+ " Return a dictionary of the available commands in the form command->class "
+ classdict = {}
+ for g in globals().values():
+ try:
+ if issubclass(g, ModTool):
+ classdict[g.name] = g
+ for a in g.aliases:
+ classdict[a] = g
+ except (TypeError, AttributeError):
+ pass
+ return classdict
+
+
+### Main code ################################################################
+def main():
+ """ Here we go. Parse command, choose class and run. """
+ cmd_dict = get_class_dict()
+ command = get_command_from_argv(cmd_dict.keys())
+ if command is None:
+ print 'Usage:' + templates.Templates['usage']
+ exit(2)
+ modtool = cmd_dict[command]()
+ modtool.setup()
+ modtool.run()
+
+if __name__ == '__main__':
+ if not ((sys.version_info[0] > 2) or
+ (sys.version_info[0] == 2 and sys.version_info[1] >= 7)):
+ print "Using Python < 2.7 is not recommended for gr_modtool."
+ try:
+ main()
+ except KeyboardInterrupt:
+ pass
+
diff --git a/gr-utils/src/python/modtool/CMakeLists.txt b/gr-utils/src/python/modtool/CMakeLists.txt
new file mode 100644
index 000000000..3425b50d0
--- /dev/null
+++ b/gr-utils/src/python/modtool/CMakeLists.txt
@@ -0,0 +1,40 @@
+# Copyright 2011 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.
+
+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_help.py
+ modtool_info.py
+ modtool_makexml.py
+ modtool_newmod.py
+ modtool_rm.py
+ newmod_tarfile.py
+ parser_cc_block.py
+ templates.py
+ util_functions.py
+ DESTINATION ${GR_PYTHON_DIR}/gnuradio/modtool
+ COMPONENT "utils"
+)
+
diff --git a/gr-utils/src/python/modtool/README.modtool b/gr-utils/src/python/modtool/README.modtool
new file mode 100644
index 000000000..93ce97292
--- /dev/null
+++ b/gr-utils/src/python/modtool/README.modtool
@@ -0,0 +1,8 @@
+gr_modtool: Swiss Army Knife for editing GNU Radio modules and -components.
+
+Adding a new subcommand
+=======================
+
+* Add a new file called modtool_SUBCOMMAND
+* Have a look at the other subcommands, it must inherit from ModTool
+* Add that file to __init__.py and CMakeLists.txt
diff --git a/gr-utils/src/python/modtool/__init__.py b/gr-utils/src/python/modtool/__init__.py
new file mode 100644
index 000000000..a10747254
--- /dev/null
+++ b/gr-utils/src/python/modtool/__init__.py
@@ -0,0 +1,34 @@
+#
+# Copyright 2012 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 code_generator import GRMTemplate
+from grc_xml_generator import GRCXMLGenerator
+from modtool_add import ModToolAdd
+from modtool_base import ModTool
+from modtool_disable import ModToolDisable
+from modtool_help import ModToolHelp
+from modtool_info import ModToolInfo
+from modtool_makexml import ModToolMakeXML
+from modtool_newmod import ModToolNewModule
+from modtool_rm import ModToolRemove
+from parser_cc_block import ParserCCBlock
+from util_functions import *
diff --git a/gr-utils/src/python/modtool/cmakefile_editor.py b/gr-utils/src/python/modtool/cmakefile_editor.py
new file mode 100644
index 000000000..fe50373bb
--- /dev/null
+++ b/gr-utils/src/python/modtool/cmakefile_editor.py
@@ -0,0 +1,97 @@
+""" Edit CMakeLists.txt files """
+
+import re
+
+### CMakeFile.txt editor class ###############################################
+class CMakeFileEditor(object):
+ """A tool for editing CMakeLists.txt files. """
+ def __init__(self, filename, separator=' ', indent=' '):
+ self.filename = filename
+ self.cfile = open(filename, 'r').read()
+ self.separator = separator
+ self.indent = indent
+
+ def get_entry_value(self, entry, to_ignore=''):
+ """ Get the value of an entry.
+ to_ignore is the part of the entry you don't care about. """
+ regexp = '%s\(%s([^()]+)\)' % (entry, to_ignore)
+ mobj = re.search(regexp, self.cfile, flags=re.MULTILINE)
+ if mobj is None:
+ return None
+ value = mobj.groups()[0].strip()
+ return value
+
+ def append_value(self, entry, value, to_ignore=''):
+ """ Add a value to an entry. """
+ regexp = re.compile('(%s\([^()]*?)\s*?(\s?%s)\)' % (entry, to_ignore),
+ re.MULTILINE)
+ substi = r'\1' + self.separator + value + r'\2)'
+ self.cfile = regexp.sub(substi, self.cfile, count=1)
+
+ def remove_value(self, entry, value, to_ignore=''):
+ """Remove a value from an entry."""
+ regexp = '^\s*(%s\(\s*%s[^()]*?\s*)%s\s*([^()]*\))' % (entry, to_ignore, value)
+ regexp = re.compile(regexp, re.MULTILINE)
+ self.cfile = re.sub(regexp, r'\1\2', self.cfile, count=1)
+
+ def delete_entry(self, entry, value_pattern=''):
+ """Remove an entry from the current buffer."""
+ regexp = '%s\s*\([^()]*%s[^()]*\)[^\n]*\n' % (entry, value_pattern)
+ regexp = re.compile(regexp, re.MULTILINE)
+ self.cfile = re.sub(regexp, '', self.cfile, count=1)
+
+ def write(self):
+ """ Write the changes back to the file. """
+ open(self.filename, 'w').write(self.cfile)
+
+ def remove_double_newlines(self):
+ """Simply clear double newlines from the file buffer."""
+ self.cfile = re.compile('\n\n\n+', re.MULTILINE).sub('\n\n', self.cfile)
+
+ def find_filenames_match(self, regex):
+ """ Find the filenames that match a certain regex
+ on lines that aren't comments """
+ filenames = []
+ reg = re.compile(regex)
+ fname_re = re.compile('[a-zA-Z]\w+\.\w{1,5}$')
+ for line in self.cfile.splitlines():
+ if len(line.strip()) == 0 or line.strip()[0] == '#':
+ continue
+ for word in re.split('[ /)(\t\n\r\f\v]', line):
+ if fname_re.match(word) and reg.search(word):
+ filenames.append(word)
+ return filenames
+
+ def disable_file(self, fname):
+ """ Comment out a file """
+ starts_line = False
+ for line in self.cfile.splitlines():
+ if len(line.strip()) == 0 or line.strip()[0] == '#':
+ continue
+ if re.search(r'\b'+fname+r'\b', line):
+ if re.match(fname, line.lstrip()):
+ starts_line = True
+ break
+ comment_out_re = r'#\1' + '\n' + self.indent
+ if not starts_line:
+ comment_out_re = r'\n' + self.indent + comment_out_re
+ (self.cfile, nsubs) = re.subn(r'(\b'+fname+r'\b)\s*', comment_out_re, self.cfile)
+ if nsubs == 0:
+ print "Warning: A replacement failed when commenting out %s. Check the CMakeFile.txt manually." % fname
+ elif nsubs > 1:
+ print "Warning: Replaced %s %d times (instead of once). Check the CMakeFile.txt manually." % (fname, nsubs)
+
+ def comment_out_lines(self, pattern, comment_str='#'):
+ """ Comments out all lines that match with pattern """
+ for line in self.cfile.splitlines():
+ if re.search(pattern, line):
+ self.cfile = self.cfile.replace(line, comment_str+line)
+
+ def check_for_glob(self, globstr):
+ """ Returns true if a glob as in globstr is found in the cmake file """
+ glob_re = r'GLOB\s[a-z_]+\s"%s"' % globstr.replace('*', '\*')
+ if re.search(glob_re, self.cfile, flags=re.MULTILINE|re.IGNORECASE) is not None:
+ return True
+ else:
+ return False
+
diff --git a/gr-utils/src/python/modtool/code_generator.py b/gr-utils/src/python/modtool/code_generator.py
new file mode 100644
index 000000000..b727f611e
--- /dev/null
+++ b/gr-utils/src/python/modtool/code_generator.py
@@ -0,0 +1,32 @@
+""" A code generator (needed by ModToolAdd) """
+
+from templates import Templates
+import Cheetah.Template
+from util_functions import str_to_fancyc_comment
+from util_functions import str_to_python_comment
+from util_functions import strip_default_values
+from util_functions import strip_arg_types
+
+### Code generator class #####################################################
+class GRMTemplate(Cheetah.Template.Template):
+ """ An extended template class """
+ def __init__(self, src, searchList):
+ self.grtypelist = {
+ 'sync': 'gr_sync_block',
+ 'sink': 'gr_sync_block',
+ 'source': 'gr_sync_block',
+ 'decimator': 'gr_sync_decimator',
+ 'interpolator': 'gr_sync_interpolator',
+ 'general': 'gr_block',
+ 'hier': 'gr_hier_block2',
+ 'noblock': ''}
+ searchList['str_to_fancyc_comment'] = str_to_fancyc_comment
+ searchList['str_to_python_comment'] = str_to_python_comment
+ searchList['strip_default_values'] = strip_default_values
+ searchList['strip_arg_types'] = strip_arg_types
+ Cheetah.Template.Template.__init__(self, src, searchList=searchList)
+ self.grblocktype = self.grtypelist[searchList['blocktype']]
+
+def get_template(tpl_id, **kwargs):
+ """ Return the template given by tpl_id, parsed through Cheetah """
+ return str(GRMTemplate(Templates[tpl_id], searchList=kwargs))
diff --git a/gr-utils/src/python/modtool/grc_xml_generator.py b/gr-utils/src/python/modtool/grc_xml_generator.py
new file mode 100644
index 000000000..2fa61863f
--- /dev/null
+++ b/gr-utils/src/python/modtool/grc_xml_generator.py
@@ -0,0 +1,85 @@
+import xml.etree.ElementTree as ET
+from util_functions import is_number, xml_indent
+
+### GRC XML Generator ########################################################
+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', 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/src/python/modtool/modtool_add.py b/gr-utils/src/python/modtool/modtool_add.py
new file mode 100644
index 000000000..c664d7c1a
--- /dev/null
+++ b/gr-utils/src/python/modtool/modtool_add.py
@@ -0,0 +1,296 @@
+""" Module to add new blocks """
+
+import os
+import sys
+import re
+from optparse import OptionGroup
+
+from util_functions import append_re_line_sequence, ask_yes_no
+from cmakefile_editor import CMakeFileEditor
+from modtool_base import ModTool
+from templates import Templates
+from code_generator import get_template
+import Cheetah.Template
+
+### Add new block module #####################################################
+class ModToolAdd(ModTool):
+ """ Add block to the out-of-tree module. """
+ name = 'add'
+ aliases = ('insert',)
+ _block_types = ('sink', 'source', 'sync', 'decimator', 'interpolator',
+ 'general', 'hier', 'noblock')
+ def __init__(self):
+ ModTool.__init__(self)
+ self._add_cc_qa = False
+ self._add_py_qa = False
+
+ def setup_parser(self):
+ parser = ModTool.setup_parser(self)
+ parser.usage = '%prog add [options]. \n Call %prog without any options to run it interactively.'
+ ogroup = OptionGroup(parser, "Add module options")
+ ogroup.add_option("-t", "--block-type", type="choice",
+ choices=self._block_types, default=None, help="One of %s." % ', '.join(self._block_types))
+ ogroup.add_option("--license-file", type="string", default=None,
+ help="File containing the license header for every source code file.")
+ ogroup.add_option("--argument-list", type="string", default=None,
+ help="The argument list for the constructor and make functions.")
+ ogroup.add_option("--add-python-qa", action="store_true", default=None,
+ help="If given, Python QA code is automatically added if possible.")
+ ogroup.add_option("--add-cpp-qa", action="store_true", default=None,
+ help="If given, C++ QA code is automatically added if possible.")
+ ogroup.add_option("--skip-cmakefiles", action="store_true", default=False,
+ help="If given, only source files are written, but CMakeLists.txt files are left unchanged.")
+ ogroup.add_option("-l", "--lang", type="choice", choices=('cpp', 'c++', 'python'),
+ default='cpp', help="Language (cpp or python)")
+ parser.add_option_group(ogroup)
+ return parser
+
+ def setup(self):
+ ModTool.setup(self)
+ options = self.options
+ self._info['blocktype'] = options.block_type
+ if self._info['blocktype'] is None:
+ while self._info['blocktype'] not in self._block_types:
+ self._info['blocktype'] = raw_input("Enter code type: ")
+ if self._info['blocktype'] not in self._block_types:
+ print 'Must be one of ' + str(self._block_types)
+ self._info['lang'] = options.lang
+ 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')):
+ print "Missing or skipping relevant subdir."
+ sys.exit(1)
+
+ if self._info['blockname'] is None:
+ if len(self.args) >= 2:
+ self._info['blockname'] = self.args[1]
+ else:
+ self._info['blockname'] = raw_input("Enter name of block/code (without module name prefix): ")
+ if not re.match('[a-zA-Z0-9_]+', self._info['blockname']):
+ print 'Invalid block name.'
+ sys.exit(2)
+ print "Block/code identifier: " + self._info['blockname']
+ self._info['fullblockname'] = self._info['modname'] + '_' + self._info['blockname']
+ self._info['license'] = self.setup_choose_license()
+
+ if options.argument_list is not None:
+ self._info['arglist'] = options.argument_list
+ else:
+ self._info['arglist'] = raw_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)
+ if self._info['version'] == 'autofoo' and not self.options.skip_cmakefiles:
+ print "Warning: Autotools modules are not supported. ",
+ print "Files will be created, but Makefiles will not be edited."
+ self.options.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.options.license_file is not None \
+ and os.path.isfile(self.options.license_file):
+ return open(self.options.license_file).read()
+ elif os.path.isfile('LICENSE'):
+ return open('LICENSE').read()
+ elif os.path.isfile('LICENCE'):
+ return open('LICENCE').read()
+ else:
+ return Templates['defaultlicense']
+
+ def _write_tpl(self, tpl, path, fname):
+ """ Shorthand for writing a substituted template to a file"""
+ print "Adding file '%s'..." % fname
+ open(os.path.join(path, fname), 'w').write(get_template(tpl, **self._info))
+
+ def run(self):
+ """ Go, go, go. """
+ has_swig = (
+ self._info['lang'] == 'cpp'
+ and not self._skip_subdirs['swig']
+ )
+ has_grc = False
+ if self._info['lang'] == 'cpp':
+ print "Traversing lib..."
+ self._run_lib()
+ has_grc = has_swig
+ else: # Python
+ print "Traversing python..."
+ self._run_python()
+ if self._info['blocktype'] != 'noblock':
+ has_grc = True
+ if has_swig:
+ print "Traversing swig..."
+ self._run_swig()
+ if self._add_py_qa:
+ print "Adding Python QA..."
+ self._run_python_qa()
+ if has_grc and not self._skip_subdirs['grc']:
+ print "Traversing 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.options.skip_cmakefiles:
+ 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 "%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'])
+ )
+ except IOError:
+ print "Can't add C++ QA files."
+ def _add_qa36():
+ " Add C++ QA files for pre-3.7 API (not autotools) "
+ fname_qa_cc = 'qa_%s.cc' % self._info['fullblockname']
+ self._write_tpl('qa_cpp36', 'lib', fname_qa_cc)
+ if not self.options.skip_cmakefiles:
+ open(self._file['cmlib'], 'a').write(
+ str(
+ Cheetah.Template.Template(
+ Templates['qa_cmakeentry36'],
+ searchList={'basename': os.path.splitext(fname_qa_cc)[0],
+ 'filename': fname_qa_cc,
+ 'modname': self._info['modname']
+ }
+ )
+ )
+ )
+ ed = CMakeFileEditor(self._file['cmlib'])
+ ed.remove_double_newlines()
+ ed.write()
+ fname_cc = None
+ fname_h = None
+ if self._info['version'] == '37':
+ fname_h = self._info['blockname'] + '.h'
+ fname_cc = self._info['blockname'] + '.cc'
+ if self._info['blocktype'] in ('source', 'sink', 'sync', 'decimator',
+ 'interpolator', 'general', 'hier'):
+ 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 not self.options.skip_cmakefiles:
+ ed = CMakeFileEditor(self._file['cmlib'])
+ ed.append_value('add_library', fname_cc)
+ ed.write()
+ ed = CMakeFileEditor(self._file['cminclude'])
+ ed.append_value('install', fname_h, 'DESTINATION[^()]+')
+ ed.write()
+ if self._add_cc_qa:
+ if self._info['version'] == '37':
+ _add_qa()
+ elif self._info['version'] == '36':
+ _add_qa36()
+ elif self._info['version'] == 'autofoo':
+ print "Warning: C++ QA files not supported for autotools."
+
+ 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 = get_template('swig_block_magic', **self._info)
+ include_str = '#include "%s%s%s.h"' % (
+ self._info['modname'],
+ 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('^%\{\n', re.MULTILINE)
+ oldfile = regexp.sub('%%{\n%s\n' % include_str, oldfile, count=1)
+ open(self._file['swig'], 'w').write(oldfile)
+ open(self._file['swig'], 'a').write(swig_block_magic_str)
+
+ 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', 'python', fname_py_qa)
+ os.chmod(os.path.join('python', fname_py_qa), 0755)
+ if self.options.skip_cmakefiles or CMakeFileEditor(self._file['cmpython']).check_for_glob('qa_*.py'):
+ return
+ print "Editing python/CMakeLists.txt..."
+ 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))
+
+ 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', 'python', fname_py)
+ append_re_line_sequence(self._file['pyinit'],
+ '(^from.*import.*\n|# import any pure.*\n)',
+ 'from %s import *' % self._info['blockname'])
+ if self.options.skip_cmakefiles:
+ return
+ ed = CMakeFileEditor(self._file['cmpython'])
+ ed.append_value('GR_PYTHON_INSTALL', fname_py, 'DESTINATION[^()]+')
+ ed.write()
+
+ 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.options.skip_cmakefiles or ed.check_for_glob('*.xml'):
+ return
+ print "Editing grc/CMakeLists.txt..."
+ ed.append_value('install', fname_grc, 'DESTINATION[^()]+')
+ ed.write()
+
diff --git a/gr-utils/src/python/modtool/modtool_base.py b/gr-utils/src/python/modtool/modtool_base.py
new file mode 100644
index 000000000..edb0f14ee
--- /dev/null
+++ b/gr-utils/src/python/modtool/modtool_base.py
@@ -0,0 +1,139 @@
+""" Base class for the modules """
+
+import os
+import re
+import sys
+from optparse import OptionParser, OptionGroup
+
+from util_functions import get_modname
+from templates import Templates
+
+### ModTool base class #######################################################
+class ModTool(object):
+ """ Base class for all modtool command classes. """
+ 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.parser = self.setup_parser()
+ self.args = None
+ self.options = None
+ self._dir = None
+
+ def setup_parser(self):
+ """ Init the option parser. If derived classes need to add options,
+ override this and call the parent function. """
+ parser = OptionParser(usage=Templates['usage'], add_help_option=False)
+ ogroup = OptionGroup(parser, "General options")
+ ogroup.add_option("-h", "--help", action="help", help="Displays this help message.")
+ ogroup.add_option("-d", "--directory", type="string", default=".",
+ help="Base directory of the module.")
+ ogroup.add_option("-n", "--module-name", type="string", default=None,
+ help="Name of the GNU Radio module. If possible, this gets detected from CMakeLists.txt.")
+ ogroup.add_option("-N", "--block-name", type="string", default=None,
+ help="Name of the block, minus the module name prefix.")
+ ogroup.add_option("--skip-lib", action="store_true", default=False,
+ help="Don't do anything in the lib/ subdirectory.")
+ ogroup.add_option("--skip-swig", action="store_true", default=False,
+ help="Don't do anything in the swig/ subdirectory.")
+ ogroup.add_option("--skip-python", action="store_true", default=False,
+ help="Don't do anything in the python/ subdirectory.")
+ ogroup.add_option("--skip-grc", action="store_true", default=False,
+ help="Don't do anything in the grc/ subdirectory.")
+ parser.add_option_group(ogroup)
+ return parser
+
+ def setup(self):
+ """ Initialise all internal variables, such as the module name etc. """
+ (options, self.args) = self.parser.parse_args()
+ self._dir = options.directory
+ if not self._check_directory(self._dir):
+ print "No GNU Radio module found in the given directory. Quitting."
+ sys.exit(1)
+ print "Operating in directory " + self._dir
+ if options.module_name is not None:
+ self._info['modname'] = options.module_name
+ else:
+ self._info['modname'] = get_modname()
+ if self._info['modname'] is None:
+ print "No GNU Radio module found in the given directory. Quitting."
+ sys.exit(1)
+ 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'])):
+ self._info['version'] = '37'
+ 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.block_name
+ self.options = options
+ self._setup_files()
+
+ 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._file['qalib'] = os.path.join('lib', 'qa_%s.cc' % self._info['modname'])
+ self._file['pyinit'] = os.path.join('python', '__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('python', 'CMakeLists.txt')
+ if self._info['version'] in ('37', 'component'):
+ 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')
+
+ 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
+ for f in files:
+ if os.path.isfile(f) and f == 'CMakeLists.txt':
+ if re.search('find_package\(GnuradioCore\)', 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 self._has_subdirs.keys()):
+ self._has_subdirs[f] = True
+ else:
+ self._skip_subdirs[f] = True
+ return bool(has_makefile and (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. """
+ pass
+
diff --git a/gr-utils/src/python/modtool/modtool_disable.py b/gr-utils/src/python/modtool/modtool_disable.py
new file mode 100644
index 000000000..67f15ad53
--- /dev/null
+++ b/gr-utils/src/python/modtool/modtool_disable.py
@@ -0,0 +1,144 @@
+""" Disable blocks module """
+
+import os
+import re
+import sys
+from optparse import OptionGroup
+
+from modtool_base import ModTool
+from cmakefile_editor import CMakeFileEditor
+
+### Disable module ###########################################################
+class ModToolDisable(ModTool):
+ """ Disable block (comments out CMake entries for files) """
+ name = 'disable'
+ aliases = ('dis',)
+ def __init__(self):
+ ModTool.__init__(self)
+
+ def setup_parser(self):
+ " Initialise the option parser for 'gr_modtool.py rm' "
+ parser = ModTool.setup_parser(self)
+ parser.usage = '%prog disable [options]. \n Call %prog without any options to run it interactively.'
+ ogroup = OptionGroup(parser, "Disable module options")
+ ogroup.add_option("-p", "--pattern", type="string", default=None,
+ help="Filter possible choices for blocks to be disabled.")
+ ogroup.add_option("-y", "--yes", action="store_true", default=False,
+ help="Answer all questions with 'yes'.")
+ parser.add_option_group(ogroup)
+ return parser
+
+ def setup(self):
+ ModTool.setup(self)
+ options = self.options
+ if options.pattern is not None:
+ self._info['pattern'] = options.pattern
+ elif options.block_name is not None:
+ self._info['pattern'] = options.block_name
+ elif len(self.args) >= 2:
+ self._info['pattern'] = self.args[1]
+ else:
+ self._info['pattern'] = raw_input('Which blocks do you want to disable? (Regex): ')
+ if len(self._info['pattern']) == 0:
+ self._info['pattern'] = '.'
+ self._info['yes'] = options.yes
+
+ def run(self):
+ """ Go, go, go! """
+ def _handle_py_qa(cmake, fname):
+ """ Do stuff for py qa """
+ cmake.comment_out_lines('GR_ADD_TEST.*'+fname)
+ return True
+ def _handle_py_mod(cmake, fname):
+ """ Do stuff for py extra files """
+ try:
+ initfile = open(self._file['pyinit']).read()
+ except IOError:
+ print "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)
+ return False
+ def _handle_cc_qa(cmake, fname):
+ """ Do stuff for cc qa """
+ if self._info['version'] == '37':
+ cmake.comment_out_lines('\$\{CMAKE_CURRENT_SOURCE_DIR\}/'+fname)
+ fname_base = os.path.splitext(fname)[0]
+ ed = CMakeFileEditor(self._file['qalib']) # Abusing the CMakeFileEditor...
+ ed.comment_out_lines('#include\s+"%s.h"' % fname_base, comment_str='//')
+ ed.comment_out_lines('%s::suite\(\)' % fname_base, comment_str='//')
+ ed.write()
+ 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])
+ return True
+ 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('(.include\s+"(%s/)?%s")' % (
+ self._info['modname'], fname),
+ r'//\1', swigfile)
+ if nsubs > 0:
+ print "Changing %s..." % 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'] == '37':
+ blockname = os.path.splitext(fname)[0]
+ (swigfile, nsubs) = re.subn('(GR_SWIG_BLOCK_MAGIC2?.+%s.+;)' % 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)
+ 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'] == '37':
+ blockname = os.path.splitext(fname)[0]
+ swigfile = re.sub('(%include\s+"'+fname+'")', r'//\1', swigfile)
+ print "Changing %s..." % self._file['swig']
+ swigfile = re.sub('(GR_SWIG_BLOCK_MAGIC2?.+'+blockname+'.+;)', r'//\1', swigfile)
+ open(self._file['swig'], 'w').write(swigfile)
+ return False
+ # List of special rules: 0: subdir, 1: filename re match, 2: function
+ special_treatments = (
+ ('python', 'qa.+py$', _handle_py_qa),
+ ('python', '^(?!qa).+py$', _handle_py_mod),
+ ('lib', 'qa.+\.cc$', _handle_cc_qa),
+ ('include/%s' % self._info['modname'], '.+\.h$', _handle_h_swig),
+ ('include', '.+\.h$', _handle_h_swig),
+ ('swig', '.+\.i$', _handle_i_swig)
+ )
+ for subdir in self._subdirs:
+ if self._skip_subdirs[subdir]: continue
+ if self._info['version'] == '37' and subdir == 'include':
+ subdir = 'include/%s' % 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']
+ for fname in filenames:
+ file_disabled = False
+ if not yes:
+ ans = raw_input("Really disable %s? [Y/n/a/q]: " % fname).lower().strip()
+ if ans == 'a':
+ yes = True
+ if ans == 'q':
+ sys.exit(0)
+ if ans == 'n':
+ continue
+ for special_treatment in special_treatments:
+ if special_treatment[0] == subdir and re.match(special_treatment[1], fname):
+ file_disabled = special_treatment[2](cmake, fname)
+ if not file_disabled:
+ cmake.disable_file(fname)
+ cmake.write()
+ print "Careful: 'gr_modtool disable' does not resolve dependencies."
+
diff --git a/gr-utils/src/python/modtool/modtool_help.py b/gr-utils/src/python/modtool/modtool_help.py
new file mode 100644
index 000000000..a1dd3c466
--- /dev/null
+++ b/gr-utils/src/python/modtool/modtool_help.py
@@ -0,0 +1,66 @@
+""" The help module """
+
+from modtool_base import ModTool
+from modtool_info import ModToolInfo
+from modtool_add import ModToolAdd
+from modtool_rm import ModToolRemove
+from modtool_newmod import ModToolNewModule
+from modtool_disable import ModToolDisable
+from modtool_makexml import ModToolMakeXML
+from util_functions import get_command_from_argv
+from templates import Templates
+
+def get_class_dict():
+ " Return a dictionary of the available commands in the form command->class "
+ classdict = {}
+ for g in globals().values():
+ try:
+ if issubclass(g, ModTool):
+ classdict[g.name] = g
+ for a in g.aliases:
+ classdict[a] = g
+ except (TypeError, AttributeError):
+ pass
+ return classdict
+
+
+### Help module ##############################################################
+def print_class_descriptions():
+ ''' Go through all ModTool* classes and print their name,
+ alias and description. '''
+ desclist = []
+ for gvar in globals().values():
+ try:
+ if issubclass(gvar, ModTool) and not issubclass(gvar, ModToolHelp):
+ desclist.append((gvar.name, ','.join(gvar.aliases), gvar.__doc__))
+ except (TypeError, AttributeError):
+ pass
+ print 'Name Aliases Description'
+ print '====================================================================='
+ for description in desclist:
+ print '%-8s %-12s %s' % description
+
+class ModToolHelp(ModTool):
+ ''' Show some help. '''
+ name = 'help'
+ aliases = ('h', '?')
+ def __init__(self):
+ ModTool.__init__(self)
+
+ def setup(self):
+ pass
+
+ def run(self):
+ cmd_dict = get_class_dict()
+ cmds = cmd_dict.keys()
+ cmds.remove(self.name)
+ for a in self.aliases:
+ cmds.remove(a)
+ help_requested_for = get_command_from_argv(cmds)
+ if help_requested_for is None:
+ print 'Usage:' + Templates['usage']
+ print '\nList of possible commands:\n'
+ print_class_descriptions()
+ return
+ cmd_dict[help_requested_for]().setup_parser().print_help()
+
diff --git a/gr-utils/src/python/modtool/modtool_info.py b/gr-utils/src/python/modtool/modtool_info.py
new file mode 100644
index 000000000..80fa27832
--- /dev/null
+++ b/gr-utils/src/python/modtool/modtool_info.py
@@ -0,0 +1,137 @@
+""" Returns information about a module """
+
+import os
+from optparse import OptionGroup
+
+from modtool_base import ModTool
+from util_functions import get_modname
+
+### Info module ##############################################################
+class ModToolInfo(ModTool):
+ """ Return information about a given module """
+ name = 'info'
+ aliases = ('getinfo', 'inf')
+ def __init__(self):
+ ModTool.__init__(self)
+
+ def setup_parser(self):
+ " Initialise the option parser for 'gr_modtool.py info' "
+ parser = ModTool.setup_parser(self)
+ parser.usage = '%prog info [options]. \n Call %prog without any options to run it interactively.'
+ ogroup = OptionGroup(parser, "Info options")
+ ogroup.add_option("--python-readable", action="store_true", default=None,
+ help="Return the output in a format that's easier to read for Python scripts.")
+ ogroup.add_option("--suggested-dirs", default=None, type="string",
+ help="Suggest typical include dirs if nothing better can be detected.")
+ parser.add_option_group(ogroup)
+ return parser
+
+ def setup(self):
+ # Won't call parent's setup(), because that's too chatty
+ (self.options, self.args) = self.parser.parse_args()
+
+ def run(self):
+ """ Go, go, go! """
+ mod_info = {}
+ mod_info['base_dir'] = self._get_base_dir(self.options.directory)
+ if mod_info['base_dir'] is None:
+ if self.options.python_readable:
+ print '{}'
+ else:
+ print "No module found."
+ exit(1)
+ os.chdir(mod_info['base_dir'])
+ mod_info['modname'] = get_modname()
+ if mod_info['modname'] is None:
+ if self.options.python_readable:
+ print '{}'
+ else:
+ print "No module found."
+ exit(1)
+ if self._info['version'] == '36' and os.path.isdir(os.path.join('include', mod_info['modname'])):
+ self._info['version'] = '37'
+ mod_info['version'] = self._info['version']
+ if 'is_component' in self._info.keys():
+ mod_info['is_component'] = True
+ mod_info['incdirs'] = []
+ mod_incl_dir = os.path.join(mod_info['base_dir'], 'include')
+ if os.path.isdir(os.path.join(mod_incl_dir, mod_info['modname'])):
+ mod_info['incdirs'].append(os.path.join(mod_incl_dir, mod_info['modname']))
+ else:
+ mod_info['incdirs'].append(mod_incl_dir)
+ build_dir = self._get_build_dir(mod_info)
+ if build_dir is not None:
+ mod_info['build_dir'] = build_dir
+ mod_info['incdirs'] += self._get_include_dirs(mod_info)
+ if self.options.python_readable:
+ print str(mod_info)
+ else:
+ self._pretty_print(mod_info)
+
+ def _get_base_dir(self, start_dir):
+ """ Figure out the base dir (where the top-level cmake file is) """
+ base_dir = os.path.abspath(start_dir)
+ if self._check_directory(base_dir):
+ return base_dir
+ else:
+ (up_dir, this_dir) = os.path.split(base_dir)
+ if os.path.split(up_dir)[1] == 'include':
+ up_dir = os.path.split(up_dir)[0]
+ if self._check_directory(up_dir):
+ return up_dir
+ return None
+
+ def _get_build_dir(self, mod_info):
+ """ Figure out the build dir (i.e. where you run 'cmake'). This checks
+ for a file called CMakeCache.txt, which is created when running cmake.
+ If that hasn't happened, the build dir cannot be detected, unless it's
+ called 'build', which is then assumed to be the build dir. """
+ base_build_dir = mod_info['base_dir']
+ if 'is_component' in mod_info.keys():
+ (base_build_dir, rest_dir) = os.path.split(base_build_dir)
+ has_build_dir = os.path.isdir(os.path.join(base_build_dir , 'build'))
+ if (has_build_dir and os.path.isfile(os.path.join(base_build_dir, 'CMakeCache.txt'))):
+ return os.path.join(base_build_dir, 'build')
+ else:
+ for (dirpath, dirnames, filenames) in os.walk(base_build_dir):
+ if 'CMakeCache.txt' in filenames:
+ return dirpath
+ if has_build_dir:
+ return os.path.join(base_build_dir, 'build')
+ return None
+
+ def _get_include_dirs(self, mod_info):
+ """ Figure out include dirs for the make process. """
+ inc_dirs = []
+ path_or_internal = {True: 'INTERNAL',
+ False: 'PATH'}['is_component' in mod_info.keys()]
+ try:
+ cmakecache_fid = open(os.path.join(mod_info['build_dir'], 'CMakeCache.txt'))
+ for line in cmakecache_fid:
+ if line.find('GNURADIO_CORE_INCLUDE_DIRS:%s' % path_or_internal) != -1:
+ inc_dirs += line.replace('GNURADIO_CORE_INCLUDE_DIRS:%s=' % path_or_internal, '').strip().split(';')
+ if line.find('GRUEL_INCLUDE_DIRS:%s' % path_or_internal) != -1:
+ inc_dirs += line.replace('GRUEL_INCLUDE_DIRS:%s=' % path_or_internal, '').strip().split(';')
+ except IOError:
+ pass
+ if len(inc_dirs) == 0 and self.options.suggested_dirs is not None:
+ inc_dirs = [os.path.normpath(path) for path in self.options.suggested_dirs.split(':') if os.path.isdir(path)]
+ return inc_dirs
+
+ def _pretty_print(self, mod_info):
+ """ Output the module info in human-readable format """
+ index_names = {'base_dir': 'Base directory',
+ 'modname': 'Module name',
+ 'is_component': 'Is GR component',
+ 'build_dir': 'Build directory',
+ 'incdirs': 'Include directories'}
+ for key in mod_info.keys():
+ if key == 'version':
+ print " API version: %s" % {
+ '36': 'pre-3.7',
+ '37': 'post-3.7',
+ 'autofoo': 'Autotools (pre-3.5)'
+ }[mod_info['version']]
+ else:
+ print '%19s: %s' % (index_names[key], mod_info[key])
+
diff --git a/gr-utils/src/python/modtool/modtool_makexml.py b/gr-utils/src/python/modtool/modtool_makexml.py
new file mode 100644
index 000000000..5a1a24f1b
--- /dev/null
+++ b/gr-utils/src/python/modtool/modtool_makexml.py
@@ -0,0 +1,151 @@
+""" Automatically create XML bindings for GRC from block code """
+
+import sys
+import os
+import re
+import glob
+from optparse import OptionGroup
+
+from modtool_base import ModTool
+from parser_cc_block import ParserCCBlock
+from grc_xml_generator import GRCXMLGenerator
+from cmakefile_editor import CMakeFileEditor
+
+### Remove module ###########################################################
+class ModToolMakeXML(ModTool):
+ """ Make XML file for GRC block bindings """
+ name = 'makexml'
+ aliases = ('mx',)
+ def __init__(self):
+ ModTool.__init__(self)
+
+ def setup_parser(self):
+ " Initialise the option parser for 'gr_modtool.py makexml' "
+ parser = ModTool.setup_parser(self)
+ parser.usage = '%prog makexml [options]. \n Call %prog without any options to run it interactively.'
+ ogroup = OptionGroup(parser, "Make XML module options")
+ ogroup.add_option("-p", "--pattern", type="string", default=None,
+ help="Filter possible choices for blocks to be parsed.")
+ ogroup.add_option("-y", "--yes", action="store_true", default=False,
+ help="Answer all questions with 'yes'. This can overwrite existing files!")
+ parser.add_option_group(ogroup)
+ return parser
+
+ def setup(self):
+ ModTool.setup(self)
+ options = self.options
+ if options.pattern is not None:
+ self._info['pattern'] = options.pattern
+ elif options.block_name is not None:
+ self._info['pattern'] = options.block_name
+ elif len(self.args) >= 2:
+ self._info['pattern'] = self.args[1]
+ else:
+ self._info['pattern'] = raw_input('Which blocks do you want to parse? (Regex): ')
+ if len(self._info['pattern']) == 0:
+ self._info['pattern'] = '.'
+ self._info['yes'] = options.yes
+
+ def run(self):
+ """ Go, go, go! """
+ print "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'] == '37':
+ files = self._search_files('lib', '*_impl.cc')
+ else:
+ files = self._search_files('lib', '*.cc')
+ for f in files:
+ 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)
+ # 2) Go through python/
+
+
+ def _search_files(self, path, path_glob):
+ """ Search for files matching pattern in the given path. """
+ files = glob.glob("%s/%s"% (path, path_glob))
+ files_filt = []
+ print "Searching for matching files in %s/:" % path
+ for f in files:
+ 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."
+ 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.
+ If necessary, add. """
+ fname_xml = '%s_%s.xml' % (self._info['modname'], blockname)
+ # 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,
+ 'type': 'int',
+ 'name': 'Num %sputs' % inout,
+ 'default': '2',
+ 'in_constructor': False})
+ if os.path.isfile(os.path.join('grc', fname_xml)):
+ # TODO add an option to keep
+ print "Warning: Overwriting existing GRC file."
+ grc_generator = GRCXMLGenerator(
+ modname=self._info['modname'],
+ blockname=blockname,
+ params=params,
+ iosig=iosig
+ )
+ grc_generator.save(os.path.join('grc', fname_xml))
+ 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, 'DESTINATION[^()]+')
+ ed.write()
+
+ def _parse_cc_h(self, fname_cc):
+ """ Go through a .cc and .h-file defining a block and return info """
+ def _type_translate(p_type, default_v=None):
+ """ Translates a type from C++ to GRC """
+ translate_dict = {'float': 'float',
+ 'double': 'real',
+ 'int': 'int',
+ 'gr_complex': 'complex',
+ 'char': 'byte',
+ 'unsigned char': 'byte',
+ 'std::string': 'string',
+ 'std::vector<int>': 'int_vector',
+ 'std::vector<float>': 'real_vector',
+ 'std::vector<gr_complex>': 'complex_vector',
+ }
+ if p_type in ('int',) and default_v[:2].lower() == '0x':
+ return 'hex'
+ try:
+ return translate_dict[p_type]
+ except KeyError:
+ return 'raw'
+ def _get_blockdata(fname_cc):
+ """ 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)
+ return (blockname, fname_h)
+ # Go, go, go
+ print "Making GRC bindings for %s..." % fname_cc
+ (blockname, fname_h) = _get_blockdata(fname_cc)
+ try:
+ parser = ParserCCBlock(fname_cc,
+ os.path.join(self._info['includedir'], fname_h),
+ blockname,
+ self._info['version'],
+ _type_translate
+ )
+ except IOError:
+ print "Can't open some of the files necessary to parse %s." % fname_cc
+ sys.exit(1)
+ return (parser.read_params(), parser.read_io_signature(), blockname)
+
+
diff --git a/gr-utils/src/python/modtool/modtool_newmod.py b/gr-utils/src/python/modtool/modtool_newmod.py
new file mode 100644
index 000000000..730b72e39
--- /dev/null
+++ b/gr-utils/src/python/modtool/modtool_newmod.py
@@ -0,0 +1,88 @@
+""" Create a whole new out-of-tree module """
+
+import os
+import re
+import sys
+import base64
+import tarfile
+from optparse import OptionGroup
+
+from modtool_base import ModTool
+from newmod_tarfile import NEWMOD_TARFILE
+
+### New out-of-tree-mod module ###############################################
+class ModToolNewModule(ModTool):
+ """ Create a new out-of-tree module """
+ name = 'newmod'
+ aliases = ('nm', 'create')
+ def __init__(self):
+ ModTool.__init__(self)
+
+ def setup_parser(self):
+ " Initialise the option parser for 'gr_modtool.py newmod' "
+ parser = ModTool.setup_parser(self)
+ parser.usage = '%prog rm [options]. \n Call %prog without any options to run it interactively.'
+ ogroup = OptionGroup(parser, "New out-of-tree module options")
+ parser.add_option_group(ogroup)
+ return parser
+
+ def setup(self):
+ (options, self.args) = self.parser.parse_args()
+ self._info['modname'] = options.module_name
+ if self._info['modname'] is None:
+ if len(self.args) >= 2:
+ self._info['modname'] = self.args[1]
+ else:
+ self._info['modname'] = raw_input('Name of the new module: ')
+ if not re.match('[a-zA-Z0-9_]+', self._info['modname']):
+ print 'Invalid module name.'
+ sys.exit(2)
+ self._dir = options.directory
+ if self._dir == '.':
+ self._dir = './gr-%s' % self._info['modname']
+ print 'Module directory is "%s".' % self._dir
+ try:
+ os.stat(self._dir)
+ except OSError:
+ pass # This is what should happen
+ else:
+ print 'The given directory exists.'
+ sys.exit(2)
+
+ def run(self):
+ """
+ * Unpack the tar.bz2 to the new locations
+ * Remove the bz2
+ * Open all files, rename howto and HOWTO to the module name
+ * Rename files and directories that contain the word howto
+ """
+ print "Creating directory..."
+ try:
+ os.mkdir(self._dir)
+ os.chdir(self._dir)
+ except OSError:
+ print 'Could not create directory %s. Quitting.' % self._dir
+ sys.exit(2)
+ print "Copying howto example..."
+ open('tmp.tar.bz2', 'wb').write(base64.b64decode(NEWMOD_TARFILE))
+ print "Unpacking..."
+ tar = tarfile.open('tmp.tar.bz2', mode='r:bz2')
+ tar.extractall()
+ tar.close()
+ os.unlink('tmp.tar.bz2')
+ print "Replacing occurences of 'howto' to '%s'..." % self._info['modname'],
+ 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."
+ print "Use 'gr_modtool add' to add a new block to this currently empty module."
+
+
diff --git a/gr-utils/src/python/modtool/modtool_rm.py b/gr-utils/src/python/modtool/modtool_rm.py
new file mode 100644
index 000000000..16bfeb34c
--- /dev/null
+++ b/gr-utils/src/python/modtool/modtool_rm.py
@@ -0,0 +1,155 @@
+""" Remove blocks module """
+
+import os
+import re
+import sys
+import glob
+from optparse import OptionGroup
+
+from util_functions import remove_pattern_from_file
+from modtool_base import ModTool
+from cmakefile_editor import CMakeFileEditor
+
+### Remove module ###########################################################
+class ModToolRemove(ModTool):
+ """ Remove block (delete files and remove Makefile entries) """
+ name = 'remove'
+ aliases = ('rm', 'del')
+ def __init__(self):
+ ModTool.__init__(self)
+
+ def setup_parser(self):
+ " Initialise the option parser for 'gr_modtool.py rm' "
+ parser = ModTool.setup_parser(self)
+ parser.usage = '%prog rm [options]. \n Call %prog without any options to run it interactively.'
+ ogroup = OptionGroup(parser, "Remove module options")
+ ogroup.add_option("-p", "--pattern", type="string", default=None,
+ help="Filter possible choices for blocks to be deleted.")
+ ogroup.add_option("-y", "--yes", action="store_true", default=False,
+ help="Answer all questions with 'yes'.")
+ parser.add_option_group(ogroup)
+ return parser
+
+ def setup(self):
+ ModTool.setup(self)
+ options = self.options
+ if options.pattern is not None:
+ self._info['pattern'] = options.pattern
+ elif options.block_name is not None:
+ self._info['pattern'] = options.block_name
+ elif len(self.args) >= 2:
+ self._info['pattern'] = self.args[1]
+ else:
+ self._info['pattern'] = raw_input('Which blocks do you want to delete? (Regex): ')
+ if len(self._info['pattern']) == 0:
+ self._info['pattern'] = '.'
+ self._info['yes'] = options.yes
+
+ def run(self):
+ """ Go, go, go! """
+ 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':
+ (base, ext) = os.path.splitext(filename)
+ if ext == '.h':
+ remove_pattern_from_file(self._file['qalib'],
+ '^#include "%s"\s*$' % filename)
+ remove_pattern_from_file(self._file['qalib'],
+ '^\s*s->addTest\(gr::%s::%s::suite\(\)\);\s*$' % (
+ self._info['modname'], base)
+ )
+ elif ext == '.cc':
+ ed.remove_value('list',
+ '\$\{CMAKE_CURRENT_SOURCE_DIR\}/%s' % filename,
+ 'APPEND test_%s_sources' % self._info['modname'])
+ else:
+ filebase = os.path.splitext(filename)[0]
+ ed.delete_entry('add_executable', filebase)
+ ed.delete_entry('target_link_libraries', filebase)
+ ed.delete_entry('GR_ADD_TEST', filebase)
+ ed.remove_double_newlines()
+
+ def _remove_py_test_case(filename=None, ed=None):
+ """ Special function that removes the occurrences of a qa*.{cc,h} file
+ from the CMakeLists.txt and the qa_$modname.cc. """
+ if filename[:2] != 'qa':
+ return
+ filebase = os.path.splitext(filename)[0]
+ ed.delete_entry('GR_ADD_TEST', filebase)
+ ed.remove_double_newlines()
+
+ 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)
+ return regexp
+ # Go, go, go!
+ if not self._skip_subdirs['lib']:
+ self._run_subdir('lib', ('*.cc', '*.h'), ('add_library',),
+ 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']:
+ 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))
+ 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'], '.*import\s+%s.*' % f[:-3])
+ remove_pattern_from_file(self._file['pyinit'], '.*from\s+%s\s+import.*\n' % f[:-3])
+ if not self._skip_subdirs['grc']:
+ self._run_subdir('grc', ('*.xml',), ('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)
+ 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
+ """
+ # 1. Create a filtered list
+ files = []
+ for g in globs:
+ files = files + glob.glob("%s/%s"% (path, g))
+ files_filt = []
+ print "Searching for matching files in %s/:" % path
+ for f in files:
+ 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."
+ return []
+ # 2. Delete files, Makefile entries and other occurences
+ files_deleted = []
+ ed = CMakeFileEditor('%s/CMakeLists.txt' % path)
+ yes = self._info['yes']
+ for f in files_filt:
+ b = os.path.basename(f)
+ if not yes:
+ ans = raw_input("Really delete %s? [Y/n/a/q]: " % f).lower().strip()
+ if ans == 'a':
+ yes = True
+ if ans == 'q':
+ sys.exit(0)
+ if ans == 'n':
+ continue
+ files_deleted.append(b)
+ print "Deleting %s." % f
+ os.unlink(f)
+ print "Deleting occurrences of %s from %s/CMakeLists.txt..." % (b, path)
+ for var in makefile_vars:
+ ed.remove_value(var, b)
+ if cmakeedit_func is not None:
+ cmakeedit_func(b, ed)
+ ed.write()
+ return files_deleted
+
diff --git a/gr-utils/src/python/modtool/newmod_tarfile.py b/gr-utils/src/python/modtool/newmod_tarfile.py
new file mode 100644
index 000000000..06d7e8f90
--- /dev/null
+++ b/gr-utils/src/python/modtool/newmod_tarfile.py
@@ -0,0 +1,1034 @@
+### The entire new module zipfile as base64 encoded tar.bz2 ###
+NEWMOD_TARFILE = """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"""
diff --git a/gr-utils/src/python/modtool/parser_cc_block.py b/gr-utils/src/python/modtool/parser_cc_block.py
new file mode 100644
index 000000000..447fe113d
--- /dev/null
+++ b/gr-utils/src/python/modtool/parser_cc_block.py
@@ -0,0 +1,205 @@
+''' A parser for blocks written in C++ '''
+import re
+import sys
+
+### Parser for CC blocks ####################################################
+def dummy_translator(the_type, default_v=None):
+ """ Doesn't really translate. """
+ return the_type
+
+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()
+ self.blockname = blockname
+ self.type_trans = type_trans
+ self.version = version
+
+ def read_io_signature(self):
+ """ Scans a .cc file for an IO signature. """
+ def _figure_out_iotype_and_vlen(iosigcall, typestr):
+ """ From a type identifier, returns the data type.
+ E.g., for sizeof(int), it will return 'int'.
+ Returns a list! """
+ if 'gr_make_iosignaturev' in iosigcall:
+ print 'tbi'
+ raise ValueError
+ return {'type': [_typestr_to_iotype(x) for x in typestr.split(',')],
+ 'vlen': [_typestr_to_vlen(x) for x in typestr.split(',')]
+ }
+ def _typestr_to_iotype(typestr):
+ """ Convert a type string (e.g. sizeof(int) * vlen) to the type (e.g. 'int'). """
+ type_match = re.search('sizeof\s*\(([^)]*)\)', typestr)
+ if type_match is None:
+ return self.type_trans('char')
+ return self.type_trans(type_match.group(1))
+ def _typestr_to_vlen(typestr):
+ """ From a type identifier, returns the vector length of the block's
+ input/out. E.g., for 'sizeof(int) * 10', it returns 10. For
+ 'sizeof(int)', it returns '1'. For 'sizeof(int) * vlen', it returns
+ the string vlen. """
+ # Catch fringe case where no sizeof() is given
+ if typestr.find('sizeof') == -1:
+ return typestr
+ if typestr.find('*') == -1:
+ return '1'
+ vlen_parts = typestr.split('*')
+ for fac in vlen_parts:
+ if fac.find('sizeof') != -1:
+ vlen_parts.remove(fac)
+ if len(vlen_parts) == 1:
+ return vlen_parts[0].strip()
+ elif len(vlen_parts) > 1:
+ return '*'.join(vlen_parts).strip()
+ iosig = {}
+ iosig_regex = '(?P<incall>gr_make_io_signature[23v]?)\s*\(\s*(?P<inmin>[^,]+),\s*(?P<inmax>[^,]+),' + \
+ '\s*(?P<intype>(\([^\)]*\)|[^)])+)\),\s*' + \
+ '(?P<outcall>gr_make_io_signature[23v]?)\s*\(\s*(?P<outmin>[^,]+),\s*(?P<outmax>[^,]+),' + \
+ '\s*(?P<outtype>(\([^\)]*\)|[^)])+)\)'
+ iosig_match = re.compile(iosig_regex, re.MULTILINE).search(self.code_cc)
+ try:
+ iosig['in'] = _figure_out_iotype_and_vlen(iosig_match.group('incall'),
+ iosig_match.group('intype'))
+ iosig['in']['min_ports'] = iosig_match.group('inmin')
+ iosig['in']['max_ports'] = iosig_match.group('inmax')
+ except ValueError, Exception:
+ print "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 ValueError, Exception:
+ print "Error: Can't parse output signature."
+ return iosig
+
+
+ def read_params(self):
+ """ Read the parameters required to initialize the block """
+ def _scan_param_list(start_idx):
+ """ Go through a parameter list and return a tuple each:
+ (type, name, default_value). Python's re just doesn't cut
+ it for C++ code :( """
+ i = start_idx
+ c = self.code_h
+ if c[i] != '(':
+ raise ValueError
+ i += 1
+
+ param_list = []
+ read_state = 'type'
+ in_string = False
+ parens_count = 0 # Counts ()
+ brackets_count = 0 # Counts <>
+ end_of_list = False
+ this_type = ''
+ this_name = ''
+ this_defv = ''
+ WHITESPACE = ' \t\n\r\f\v'
+ while not end_of_list:
+ # Keep track of (), stop when reaching final closing parens
+ if not in_string:
+ if c[i] == ')':
+ if parens_count == 0:
+ if read_state == 'type':
+ raise ValueError(
+ 'Found closing parentheses before finishing last argument (this is how far I got: %s)'
+ % str(param_list)
+ )
+ param_list.append((this_type, this_name, this_defv))
+ end_of_list = True
+ break
+ else:
+ parens_count -= 1
+ elif c[i] == '(':
+ parens_count += 1
+ # Parameter type (int, const std::string, std::vector<gr_complex>, unsigned long ...)
+ if read_state == 'type':
+ if c[i] == '<':
+ brackets_count += 1
+ if c[i] == '>':
+ brackets_count -= 1
+ if c[i] == '&':
+ i += 1
+ continue
+ if c[i] in WHITESPACE and brackets_count == 0:
+ while c[i] in WHITESPACE:
+ i += 1
+ continue
+ if this_type == 'const' or this_type == '': # Ignore this
+ this_type = ''
+ elif this_type == 'unsigned': # Continue
+ this_type += ' '
+ continue
+ else:
+ read_state = 'name'
+ continue
+ this_type += c[i]
+ i += 1
+ continue
+ # Parameter name
+ if read_state == 'name':
+ if c[i] == '&' or c[i] in WHITESPACE:
+ i += 1
+ 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)
+ )
+ 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)
+ )
+ read_state = 'defv'
+ else:
+ this_name += c[i]
+ i += 1
+ continue
+ # Default value
+ if read_state == 'defv':
+ if in_string:
+ if c[i] == '"' and c[i-1] != '\\':
+ in_string = False
+ else:
+ this_defv += c[i]
+ 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)
+ )
+ read_state = 'type'
+ param_list.append((this_type, this_name, this_defv))
+ this_type = ''
+ this_name = ''
+ this_defv = ''
+ else:
+ this_defv += c[i]
+ i += 1
+ continue
+ return param_list
+ # Go, go, go!
+ if self.version == '37':
+ make_regex = 'static\s+sptr\s+make\s*'
+ else:
+ make_regex = '(?<=_API)\s+\w+_sptr\s+\w+_make_\w+\s*'
+ make_match = re.compile(make_regex, re.MULTILINE).search(self.code_h)
+ try:
+ params_list = _scan_param_list(make_match.end(0))
+ except ValueError as ve:
+ print "Can't parse the argument list: ", ve.args[0]
+ sys.exit(0)
+ params = []
+ for plist in params_list:
+ params.append({'type': self.type_trans(plist[0], plist[2]),
+ 'key': plist[1],
+ 'default': plist[2],
+ 'in_constructor': True})
+ return params
+
diff --git a/gr-utils/src/python/modtool/templates.py b/gr-utils/src/python/modtool/templates.py
new file mode 100644
index 000000000..e3019bb70
--- /dev/null
+++ b/gr-utils/src/python/modtool/templates.py
@@ -0,0 +1,698 @@
+''' All the templates for skeleton files (needed by ModToolAdd) '''
+
+from datetime import datetime
+
+### Templates ################################################################
+Templates = {}
+Templates36 = {}
+
+# Default licence
+Templates['defaultlicense'] = '''
+Copyright %d <+YOU OR YOUR COMPANY+>.
+
+This 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.
+
+This software 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 this software; see the file COPYING. If not, write to
+the Free Software Foundation, Inc., 51 Franklin Street,
+Boston, MA 02110-1301, USA.
+''' % datetime.now().year
+
+# Header file of a sync/decimator/interpolator block
+Templates['block_impl_h'] = '''/* -*- c++ -*- */
+${str_to_fancyc_comment($license)}
+\#ifndef INCLUDED_${modname.upper()}_${blockname.upper()}_IMPL_H
+\#define INCLUDED_${modname.upper()}_${blockname.upper()}_IMPL_H
+
+\#include <${modname}/${blockname}.h>
+
+namespace gr {
+ namespace ${modname} {
+
+ class ${blockname}_impl : public ${blockname}
+ {
+ private:
+ // Nothing to declare in this block.
+
+ public:
+ ${blockname}_impl(${strip_default_values($arglist)});
+ ~${blockname}_impl();
+
+#if $blocktype == 'general'
+ void forecast (int noutput_items, gr_vector_int &ninput_items_required);
+
+ // Where all the action really happens
+ int general_work(int noutput_items,
+ gr_vector_int &ninput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items);
+#else if $blocktype == 'hier'
+#silent pass
+#else
+ // Where all the action really happens
+ int work(int noutput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items);
+#end if
+ };
+
+ } // namespace ${modname}
+} // namespace gr
+
+\#endif /* INCLUDED_${modname.upper()}_${blockname.upper()}_IMPL_H */
+
+'''
+
+# C++ file of a GR block
+Templates['block_impl_cpp'] = '''/* -*- c++ -*- */
+${str_to_fancyc_comment($license)}
+\#ifdef HAVE_CONFIG_H
+\#include "config.h"
+\#endif
+
+\#include <gr_io_signature.h>
+#if $blocktype == 'noblock'
+\#include <${modname}/${blockname}.h>
+#else
+\#include "${blockname}_impl.h"
+#end if
+
+namespace gr {
+ namespace ${modname} {
+
+#if $blocktype == 'noblock'
+ $blockname::${blockname}(${strip_default_values($arglist)})
+ {
+ }
+
+ $blockname::~${blockname}()
+ {
+ }
+#else
+ ${blockname}::sptr
+ ${blockname}::make(${strip_default_values($arglist)})
+ {
+ return gnuradio::get_initial_sptr (new ${blockname}_impl(${strip_arg_types($arglist)}));
+ }
+
+#if $blocktype == 'decimator'
+#set $decimation = ', <+decimation+>'
+#else if $blocktype == 'interpolator'
+#set $decimation = ', <+interpolation+>'
+#else
+#set $decimation = ''
+#end if
+#if $blocktype == 'source'
+#set $inputsig = '0, 0, 0'
+#else
+#set $inputsig = '<+MIN_IN+>, <+MAX_IN+>, sizeof (<+float+>)'
+#end if
+#if $blocktype == 'sink'
+#set $outputsig = '0, 0, 0'
+#else
+#set $outputsig = '<+MIN_IN+>, <+MAX_IN+>, sizeof (<+float+>)'
+#end if
+ /*
+ * The private constructor
+ */
+ ${blockname}_impl::${blockname}_impl(${strip_default_values($arglist)})
+ : ${grblocktype}("${blockname}",
+ gr_make_io_signature($inputsig),
+ gr_make_io_signature($outputsig)$decimation)
+#if $blocktype == 'hier'
+ {
+ connect(self(), 0, d_firstblock, 0);
+ // connect other blocks
+ connect(d_lastblock, 0, self(), 0);
+ }
+#else
+ {}
+#end if
+
+ /*
+ * Our virtual destructor.
+ */
+ ${blockname}_impl::~${blockname}_impl()
+ {
+ }
+
+#if $blocktype == 'general'
+ void
+ ${blockname}_impl::forecast (int noutput_items, gr_vector_int &ninput_items_required)
+ {
+ /* <+forecast+> e.g. ninput_items_required[0] = noutput_items */
+ }
+
+ int
+ ${blockname}_impl::general_work (int noutput_items,
+ gr_vector_int &ninput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items)
+ {
+ const float *in = (const float *) input_items[0];
+ float *out = (float *) output_items[0];
+
+ // Do <+signal processing+>
+ // Tell runtime system how many input items we consumed on
+ // each input stream.
+ consume_each (noutput_items);
+
+ // Tell runtime system how many output items we produced.
+ return noutput_items;
+ }
+#else if $blocktype == 'hier'
+#silent pass
+#else
+ int
+ ${blockname}_impl::work(int noutput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items)
+ {
+ const float *in = (const float *) input_items[0];
+ float *out = (float *) output_items[0];
+
+ // Do <+signal processing+>
+
+ // Tell runtime system how many output items we produced.
+ return noutput_items;
+ }
+#end if
+#end if
+
+ } /* namespace ${modname} */
+} /* namespace gr */
+
+'''
+
+# Block definition header file (for include/)
+Templates['block_def_h'] = '''/* -*- c++ -*- */
+${str_to_fancyc_comment($license)}
+
+\#ifndef INCLUDED_${modname.upper()}_${blockname.upper()}_H
+\#define INCLUDED_${modname.upper()}_${blockname.upper()}_H
+
+\#include <${modname}/api.h>
+\#include <${grblocktype}.h>
+
+namespace gr {
+ namespace ${modname} {
+
+#if $blocktype == 'noblock'
+ /*!
+ * \\brief <+description+>
+ *
+ */
+ class ${modname.upper()}_API $blockname
+ {
+ ${blockname}(${arglist});
+ ~${blockname}();
+ private:
+ };
+#else
+ /*!
+ * \\brief <+description of block+>
+ * \ingroup ${modname}
+ *
+ */
+ class ${modname.upper()}_API ${blockname} : virtual public $grblocktype
+ {
+ public:
+ typedef boost::shared_ptr<${blockname}> sptr;
+
+ /*!
+ * \\brief Return a shared_ptr to a new instance of ${modname}::${blockname}.
+ *
+ * To avoid accidental use of raw pointers, ${modname}::${blockname}'s
+ * constructor is in a private implementation
+ * class. ${modname}::${blockname}::make is the public interface for
+ * creating new instances.
+ */
+ static sptr make($arglist);
+ };
+#end if
+
+ } // namespace ${modname}
+} // namespace gr
+
+\#endif /* INCLUDED_${modname.upper()}_${blockname.upper()}_H */
+
+'''
+
+# Python block (from grextras!)
+Templates['block_python'] = '''\#!/usr/bin/env python
+${str_to_python_comment($license)}
+#
+#if $blocktype == 'noblock'
+#stop
+#end if
+
+#if $blocktype in ('sync', 'sink', 'source')
+#set $parenttype = 'gr.sync_block'
+#else
+#set $parenttype = {'hier': 'gr.hier_block2', 'interpolator': 'gr.interp_block', 'decimator': 'gr.decim_block', 'general': 'gr.block'}[$blocktype]
+#end if
+#if $blocktype != 'hier'
+import numpy
+#if $blocktype == 'source'
+#set $inputsig = 'None'
+#else
+#set $inputsig = '[<+numpy.float+>]'
+#end if
+#if $blocktype == 'sink'
+#set $outputsig = 'None'
+#else
+#set $outputsig = '[<+numpy.float+>]'
+#end if
+#else
+#if $blocktype == 'source'
+#set $inputsig = '0, 0, 0'
+#else
+#set $inputsig = '<+MIN_IN+>, <+MAX_IN+>, gr.sizeof_<+float+>'
+#end if
+#if $blocktype == 'sink'
+#set $outputsig = '0, 0, 0'
+#else
+#set $outputsig = '<+MIN_OUT+>, <+MAX_OUT+>, gr.sizeof_<+float+>'
+#end if
+#end if
+#if $blocktype == 'interpolator'
+#set $deciminterp = ', <+interpolation+>'
+#else if $blocktype == 'decimator'
+#set $deciminterp = ', <+decimation+>'
+#set $deciminterp = ''
+#else
+#end if
+from gnuradio import gr
+
+class ${blockname}(${parenttype}):
+ """
+ docstring for block ${blockname}
+ """
+ def __init__(self#if $arglist == '' then '' else ', '#$arglist):
+ gr.${parenttype}.__init__(self,
+#if $blocktype == 'hier'
+ "$blockname",
+ gr.io_signature(${inputsig}), # Input signature
+ gr.io_signature(${outputsig})) # Output signature
+
+ # Define blocks and connect them
+ self.connect()
+#stop
+#else
+ name="${blockname}",
+ in_sig=${inputsig},
+ out_sig=${outputsig}${deciminterp})
+#end if
+
+#if $blocktype == 'general'
+ def forecast(self, noutput_items, ninput_items_required):
+ #setup size of input_items[i] for work call
+ for i in range(len(ninput_items_required)):
+ ninput_items_required[i] = noutput_items
+
+ 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]))
+ return len(output_items[0])
+#stop
+#else
+ def work(self, input_items, output_items):
+#end if
+
+ def work(self, input_items, output_items):
+#if $blocktype != 'source'
+ in0 = input_items[0]
+#end if
+#if $blocktype != 'sink'
+ out = output_items[0]
+#end if
+ # <+signal processing here+>
+#if $blocktype in ('sync', 'decimator', 'interpolator')
+ out[:] = in0
+ return len(output_items[0])
+#else if $blocktype == 'sink'
+ return len(input_items[0])
+#else if $blocktype == 'source'
+ out[:] = whatever
+ return len(output_items[0])
+#end if
+
+'''
+
+# C++ file for QA
+Templates['qa_cpp'] = '''/* -*- c++ -*- */
+${str_to_fancyc_comment($license)}
+
+\#include "qa_${blockname}.h"
+\#include <cppunit/TestAssert.h>
+
+\#include <$modname/${blockname}.h>
+
+namespace gr {
+ namespace ${modname} {
+
+ void
+ qa_${blockname}::t1()
+ {
+ // Put test here
+ }
+
+ } /* namespace ${modname} */
+} /* namespace gr */
+
+'''
+
+# Header file for QA
+Templates['qa_h'] = '''/* -*- c++ -*- */
+${str_to_fancyc_comment($license)}
+
+\#ifndef _QA_${blockname.upper()}_H_
+\#define _QA_${blockname.upper()}_H_
+
+\#include <cppunit/extensions/HelperMacros.h>
+\#include <cppunit/TestCase.h>
+
+namespace gr {
+ namespace ${modname} {
+
+ class qa_${blockname} : public CppUnit::TestCase
+ {
+ public:
+ CPPUNIT_TEST_SUITE(qa_${blockname});
+ CPPUNIT_TEST(t1);
+ CPPUNIT_TEST_SUITE_END();
+
+ private:
+ void t1();
+ };
+
+ } /* namespace ${modname} */
+} /* namespace gr */
+
+\#endif /* _QA_${blockname.upper()}_H_ */
+
+'''
+
+# Python QA code
+Templates['qa_python'] = '''\#!/usr/bin/env python
+${str_to_python_comment($license)}
+#
+
+from gnuradio import gr, gr_unittest
+#if $lang == 'cpp'
+import ${modname}_swig as ${modname}
+#else
+from ${blockname} import ${blockname}
+#end if
+
+class qa_$blockname (gr_unittest.TestCase):
+
+ def setUp (self):
+ self.tb = gr.top_block ()
+
+ def tearDown (self):
+ self.tb = None
+
+ def test_001_t (self):
+ # set up fg
+ self.tb.run ()
+ # check data
+
+
+if __name__ == '__main__':
+ gr_unittest.run(qa_${blockname}, "qa_${blockname}.xml")
+'''
+
+Templates['grc_xml'] = '''<?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($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, real, 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, real, complex, byte, short, xxx_vector, ...--></type>
+ </source>
+</block>
+'''
+
+# Usage
+Templates['usage'] = '''
+gr_modtool.py <command> [options] -- Run <command> with the given options.
+gr_modtool.py help -- Show a list of commands.
+gr_modtool.py help <command> -- Shows the help for a given command. '''
+
+# SWIG string
+Templates['swig_block_magic'] = """#if $version == '37'
+#set $mod_block_sep = '/'
+#set $block_magic_version = '2'
+#else
+#set $mod_block_sep = '_'
+#set $block_magic_version = ''
+#end if
+%include "${modname}${mod_block_sep}${blockname}.h"
+GR_SWIG_BLOCK_MAGIC${block_magic_version}($modname, $blockname);
+"""
+
+## Old stuff
+# C++ file of a GR block
+Templates['block_cpp36'] = '''/* -*- c++ -*- */
+${str_to_fancyc_comment($license)}
+\#ifdef HAVE_CONFIG_H
+\#include "config.h"
+\#endif
+
+#if $blocktype != 'noblock'
+\#include <gr_io_signature.h>
+#end if
+\#include "${modname}_${blockname}.h"
+
+#if $blocktype == 'noblock'
+${modname}_${blockname}::${modname}_${blockname}(${strip_default_values($arglist)})
+{
+}
+
+${modname}_${blockname}::~${modname}_${blockname}()
+{
+}
+#else
+${modname}_${blockname}_sptr
+${modname}_make_${blockname} (${strip_default_values($arglist)})
+{
+ return gnuradio::get_initial_sptr (new ${modname}_${blockname}(${strip_arg_types($arglist)}));
+}
+
+#if $blocktype == 'decimator'
+#set $decimation = ', <+decimation+>'
+#else if $blocktype == 'interpolator'
+#set $decimation = ', <+interpolation+>'
+#else
+#set $decimation = ''
+#end if
+#if $blocktype == 'sink'
+#set $inputsig = '0, 0, 0'
+#else
+#set $inputsig = '<+MIN_IN+>, <+MAX_IN+>, sizeof (<+float+>)'
+#end if
+#if $blocktype == 'source'
+#set $outputsig = '0, 0, 0'
+#else
+#set $outputsig = '<+MIN_OUT+>, <+MAX_OUT+>, sizeof (<+float+>)'
+#end if
+
+/*
+ * The private constructor
+ */
+${modname}_${blockname}::${modname}_${blockname} (${strip_default_values($arglist)})
+ : ${grblocktype} ("${blockname}",
+ gr_make_io_signature($inputsig),
+ gr_make_io_signature($outputsig)$decimation)
+{
+#if $blocktype == 'hier'
+ connect(self(), 0, d_firstblock, 0);
+ // <+connect other blocks+>
+ connect(d_lastblock, 0, self(), 0);
+#else
+ // Put in <+constructor stuff+> here
+#end if
+}
+
+
+/*
+ * Our virtual destructor.
+ */
+${modname}_${blockname}::~${modname}_${blockname}()
+{
+ // Put in <+destructor stuff+> here
+}
+#end if
+
+
+#if $blocktype == 'general'
+void
+${modname}_${blockname}::forecast (int noutput_items, gr_vector_int &ninput_items_required)
+{
+ /* <+forecast+> e.g. ninput_items_required[0] = noutput_items */
+}
+
+int
+${modname}_${blockname}::general_work (int noutput_items,
+ gr_vector_int &ninput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items)
+{
+ const float *in = (const float *) input_items[0];
+ float *out = (float *) output_items[0];
+
+ // Do <+signal processing+>
+ // Tell runtime system how many input items we consumed on
+ // each input stream.
+ consume_each (noutput_items);
+
+ // Tell runtime system how many output items we produced.
+ return noutput_items;
+}
+#else if $blocktype == 'hier' or $blocktype == 'noblock'
+#pass
+#else
+int
+${modname}_${blockname}::work(int noutput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items)
+{
+ const float *in = (const float *) input_items[0];
+ float *out = (float *) output_items[0];
+
+ // Do <+signal processing+>
+
+ // Tell runtime system how many output items we produced.
+ return noutput_items;
+}
+#end if
+
+'''
+
+# Block definition header file (for include/)
+Templates['block_h36'] = '''/* -*- c++ -*- */
+${str_to_fancyc_comment($license)}
+
+\#ifndef INCLUDED_${modname.upper()}_${blockname.upper()}_H
+\#define INCLUDED_${modname.upper()}_${blockname.upper()}_H
+
+\#include <${modname}_api.h>
+#if $blocktype == 'noblock'
+class ${modname.upper()}_API $blockname
+{
+ ${blockname}(${arglist});
+ ~${blockname}();
+ private:
+};
+
+#else
+\#include <${grblocktype}.h>
+
+class ${modname}_${blockname};
+
+typedef boost::shared_ptr<${modname}_${blockname}> ${modname}_${blockname}_sptr;
+
+${modname.upper()}_API ${modname}_${blockname}_sptr ${modname}_make_${blockname} ($arglist);
+
+/*!
+ * \\brief <+description+>
+ * \ingroup ${modname}
+ *
+ */
+class ${modname.upper()}_API ${modname}_${blockname} : public $grblocktype
+{
+ private:
+ friend ${modname.upper()}_API ${modname}_${blockname}_sptr ${modname}_make_${blockname} (${strip_default_values($arglist)});
+
+ ${modname}_${blockname}(${strip_default_values($arglist)});
+
+ public:
+ ~${modname}_${blockname}();
+
+#if $blocktype == 'general'
+ void forecast (int noutput_items, gr_vector_int &ninput_items_required);
+
+ // Where all the action really happens
+ int general_work (int noutput_items,
+ gr_vector_int &ninput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items);
+#else if $blocktype == 'hier'
+#pass
+#else
+ // Where all the action really happens
+ int work (int noutput_items,
+ gr_vector_const_void_star &input_items,
+ gr_vector_void_star &output_items);
+#end if
+};
+#end if
+
+\#endif /* INCLUDED_${modname.upper()}_${blockname.upper()}_H */
+
+'''
+
+# C++ file for QA
+Templates['qa_cpp36'] = '''/* -*- c++ -*- */
+${str_to_fancyc_comment($license)}
+
+\#include <boost/test/unit_test.hpp>
+
+BOOST_AUTO_TEST_CASE(qa_${modname}_${blockname}_t1){
+ BOOST_CHECK_EQUAL(2 + 2, 4);
+ // TODO BOOST_* test macros here
+}
+
+BOOST_AUTO_TEST_CASE(qa_${modname}_${blockname}_t2){
+ BOOST_CHECK_EQUAL(2 + 2, 4);
+ // TODO BOOST_* test macros here
+}
+
+'''
+
+# Header file for QA
+Templates['qa_cmakeentry36'] = """
+add_executable($basename $filename)
+target_link_libraries($basename gnuradio-$modname \${Boost_LIBRARIES})
+GR_ADD_TEST($basename $basename)
+"""
+
diff --git a/gr-utils/src/python/modtool/util_functions.py b/gr-utils/src/python/modtool/util_functions.py
new file mode 100644
index 000000000..029ae04bf
--- /dev/null
+++ b/gr-utils/src/python/modtool/util_functions.py
@@ -0,0 +1,127 @@
+""" Utility functions for gr_modtool.py """
+
+import re
+import sys
+
+### Utility functions ########################################################
+def get_command_from_argv(possible_cmds):
+ """ Read the requested command from argv. This can't be done with optparse,
+ since the option parser isn't defined before the command is known, and
+ optparse throws an error."""
+ command = None
+ for arg in sys.argv:
+ if arg[0] == "-":
+ continue
+ else:
+ command = arg
+ if command in possible_cmds:
+ return arg
+ return None
+
+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()
+ lines = re.findall(linepattern, oldfile, flags=re.MULTILINE)
+ if len(lines) == 0:
+ open(filename, 'a').write(newline)
+ return
+ last_line = lines[-1]
+ newfile = oldfile.replace(last_line, last_line + newline + '\n')
+ open(filename, 'w').write(newfile)
+
+def remove_pattern_from_file(filename, pattern):
+ """ Remove all occurrences of a given pattern from a file. """
+ oldfile = open(filename, 'r').read()
+ pattern = re.compile(pattern, re.MULTILINE)
+ open(filename, 'w').write(pattern.sub('', oldfile))
+
+def str_to_fancyc_comment(text):
+ """ Return a string as a C formatted comment. """
+ l_lines = text.splitlines()
+ outstr = "/* " + l_lines[0] + "\n"
+ for line in l_lines[1:]:
+ outstr += " * " + line + "\n"
+ outstr += " */\n"
+ return outstr
+
+def str_to_python_comment(text):
+ """ Return a string as a Python formatted comment. """
+ return re.compile('^', re.MULTILINE).sub('# ', text)
+
+def strip_default_values(string):
+ """ Strip default values from a C++ argument list. """
+ return re.sub(' *=[^,)]*', '', string)
+
+def strip_arg_types(string):
+ """" Strip the argument types from a list of arguments
+ Example: "int arg1, double arg2" -> "arg1, arg2" """
+ string = strip_default_values(string)
+ return ", ".join([part.strip().split(' ')[-1] for part in string.split(',')])
+
+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()
+ 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()
+ regexp = r'(project\s*\(\s*|GR_REGISTER_COMPONENT\(")gr-(?P<modname>[a-zA-Z1-9-_]+)(\s*(CXX)?|" ENABLE)'
+ try:
+ modname = re.search(regexp, cmfile, flags=re.MULTILINE).group('modname').strip()
+ if modname in modname_trans.keys():
+ modname = modname_trans[modname]
+ return modname
+ except AttributeError:
+ return None
+
+def get_class_dict():
+ " Return a dictionary of the available commands in the form command->class "
+ classdict = {}
+ for g in globals().values():
+ try:
+ if issubclass(g, ModTool):
+ classdict[g.name] = g
+ for a in g.aliases:
+ classdict[a] = g
+ except (TypeError, AttributeError):
+ pass
+ return classdict
+
+def is_number(s):
+ " Return True if the string s contains a number. "
+ try:
+ float(s)
+ return True
+ 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. """
+ question += {True: ' [Y/n] ', False: ' [y/N] '}[default]
+ if raw_input(question).lower() != {True: 'n', False: 'y'}[default]:
+ return default
+ else:
+ return not default