summaryrefslogtreecommitdiff
path: root/grc/core
diff options
context:
space:
mode:
authorJosh Morman <jmorman@perspectalabs.com>2019-08-30 09:47:42 -0400
committerdevnulling <devnulling@users.noreply.github.com>2020-02-10 13:33:07 -0800
commit3232eeb534b5ac1ab503f7f36254227b1cfba1f9 (patch)
treec950d0e841644ed584737e905e315d778fe26f3e /grc/core
parent838d081918f8e5ee3f28273237d8b74a8c0b9d9b (diff)
grc: add python snippets to GRC
This feature adds the ability to insert arbitrary code into the python flowgraph. It gives a little more low-level flexibility for quickly modifying flowgraphs and adding custom bits of code rather than having to go and edit the generated py file One example is synchronizing multiple USRP objects - sometimes you want different sync than what is offered in the multi-usrp object, so you can put a bit of code in the snippet block to do the custom synchronization
Diffstat (limited to 'grc/core')
-rw-r--r--grc/core/FlowGraph.py41
-rw-r--r--grc/core/base.py1
-rw-r--r--grc/core/blocks/block.py4
-rw-r--r--grc/core/generator/flow_graph.py.mako43
-rw-r--r--grc/core/generator/top_block.py2
-rw-r--r--grc/core/params/param.py10
6 files changed, 95 insertions, 6 deletions
diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py
index 21c3cdb59d..b0141f8705 100644
--- a/grc/core/FlowGraph.py
+++ b/grc/core/FlowGraph.py
@@ -81,6 +81,47 @@ class FlowGraph(Element):
parameters = [b for b in self.iter_enabled_blocks() if b.key == 'parameter']
return parameters
+ def get_snippets(self):
+ """
+ Get a set of all code snippets (Python) in this flow graph namespace.
+
+ Returns:
+ a list of code snippets
+ """
+ return [b for b in self.iter_enabled_blocks() if b.key == 'snippet']
+
+ def get_snippets_dict(self, section=None):
+ """
+ Get a dictionary of code snippet information for a particular section.
+
+ Args:
+ section: string specifier of section of snippets to return, section=None returns all
+
+ Returns:
+ a list of code snippets dicts
+ """
+ snippets = self.get_snippets()
+ if not snippets:
+ return []
+
+ output = []
+ for snip in snippets:
+ d ={}
+ sect = snip.params['section'].value
+ d['section'] = sect
+ d['priority'] = snip.params['priority'].value
+ d['lines'] = snip.params['code'].value.splitlines()
+ d['def'] = 'def snipfcn_{}(self):'.format(snip.name)
+ d['call'] = 'snipfcn_{}(tb)'.format(snip.name)
+ if not section or sect == section:
+ output.append(d)
+
+ # Sort by descending priority
+ if section:
+ output = sorted(output, key=lambda x: x['priority'], reverse=True)
+
+ return output
+
def get_monitors(self):
"""
Get a list of all ControlPort monitors
diff --git a/grc/core/base.py b/grc/core/base.py
index dae9ea229b..a5de8837d6 100644
--- a/grc/core/base.py
+++ b/grc/core/base.py
@@ -139,6 +139,7 @@ class Element(object):
is_param = False
is_variable = False
is_import = False
+ is_snippet = False
def get_raw(self, name):
descriptor = getattr(self.__class__, name, None)
diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py
index ff1c8befc3..e51b730cdd 100644
--- a/grc/core/blocks/block.py
+++ b/grc/core/blocks/block.py
@@ -293,6 +293,10 @@ class Block(Element):
def is_import(self):
return self.key == 'import'
+ @lazy_property
+ def is_snippet(self):
+ return self.key == 'snippet'
+
@property
def comment(self):
return self.params['comment'].value
diff --git a/grc/core/generator/flow_graph.py.mako b/grc/core/generator/flow_graph.py.mako
index 7e731d1a9d..3635c550db 100644
--- a/grc/core/generator/flow_graph.py.mako
+++ b/grc/core/generator/flow_graph.py.mako
@@ -42,6 +42,7 @@ if __name__ == '__main__':
##${imp.replace(" # grc-generated hier_block", "")}
${imp}
% endfor
+
########################################################
##Create Class
## Write the class declaration for a top or hier block.
@@ -219,6 +220,7 @@ gr.io_signaturev(${len(io_sigs)}, ${len(io_sigs)}, [${', '.join(size_strs)}])\
${ connection.rstrip() }
% endfor
% endif
+
########################################################
## QT sink close method reimplementation
########################################################
@@ -266,6 +268,32 @@ gr.io_signaturev(${len(io_sigs)}, ${len(io_sigs)}, [${', '.join(size_strs)}])\
% endfor
% endif
% endfor
+\
+% for snip in flow_graph.get_snippets_dict():
+
+${indent(snip['def'])}
+% for line in snip['lines']:
+ ${indent(line)}
+% endfor
+% endfor
+\
+<%
+snippet_sections = ['main_after_init', 'main_after_start', 'main_after_stop']
+snippets = {}
+for section in snippet_sections:
+ snippets[section] = flow_graph.get_snippets_dict(section)
+%>
+\
+%for section in snippet_sections:
+%if snippets[section]:
+
+def snippets_${section}(tb):
+ % for snip in snippets[section]:
+ ${indent(snip['call'])}
+ % endfor
+%endif
+%endfor
+
########################################################
##Create Main
## For top block code, generate a main routine.
@@ -335,9 +363,11 @@ def main(top_block_cls=${class_name}, options=None):
qapp = Qt.QApplication(sys.argv)
tb = top_block_cls(${ ', '.join(params_eq_list) })
+ ${'snippets_main_after_init(tb)' if snippets['main_after_init'] else ''}
% if flow_graph.get_option('run'):
tb.start(${flow_graph.get_option('max_nouts') or ''})
% endif
+ ${'snippets_main_after_start(tb)' if snippets['main_after_start'] else ''}
% if flow_graph.get_option('qt_qss_theme'):
tb.setStyleSheetFromFile("${ flow_graph.get_option('qt_qss_theme') }")
% endif
@@ -356,6 +386,7 @@ def main(top_block_cls=${class_name}, options=None):
def quitting():
tb.stop()
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
qapp.aboutToQuit.connect(quitting)
% for m in monitors:
% if m.params['en'].get_value() == 'True':
@@ -368,6 +399,7 @@ def main(top_block_cls=${class_name}, options=None):
def killProc(signum, frame, tb):
tb.stop()
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
serverProc.terminate()
serverProc.kill()
time.sleep(1)
@@ -383,20 +415,23 @@ def main(top_block_cls=${class_name}, options=None):
url = "http://localhost:" + port + "/bokehgui")
# Create Top Block instance
tb = top_block_cls(doc)
+ ${'snippets_main_after_init(tb)' if snippets['main_after_init'] else ''}
try:
tb.start()
+ ${'snippets_main_after_start(tb)' if snippets['main_after_start'] else ''}
signal.signal(signal.SIGTERM, functools.partial(killProc, tb=tb))
session.loop_until_closed()
finally:
print("Exiting the simulation. Stopping Bokeh Server")
tb.stop()
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
finally:
serverProc.terminate()
serverProc.kill()
% elif generate_options == 'no_gui':
tb = top_block_cls(${ ', '.join(params_eq_list) })
-
+ ${'snippets_main_after_init(tb)' if snippets['main_after_init'] else ''}
def sig_handler(sig=None, frame=None):
% for m in monitors:
% if m.params['en'].get_value() == 'True':
@@ -405,6 +440,7 @@ def main(top_block_cls=${class_name}, options=None):
% endfor
tb.stop()
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
sys.exit(0)
signal.signal(signal.SIGINT, sig_handler)
@@ -412,6 +448,7 @@ def main(top_block_cls=${class_name}, options=None):
% if flow_graph.get_option('run_options') == 'prompt':
tb.start(${ flow_graph.get_option('max_nouts') or '' })
+ ${'snippets_main_after_start(tb)' if snippets['main_after_start'] else ''}
% for m in monitors:
% if m.params['en'].get_value() == 'True':
tb.${m.name}.start()
@@ -422,8 +459,10 @@ def main(top_block_cls=${class_name}, options=None):
except EOFError:
pass
tb.stop()
+ ## ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
% elif flow_graph.get_option('run_options') == 'run':
tb.start(${flow_graph.get_option('max_nouts') or ''})
+ ${'snippets_main_after_start(tb)' if snippets['main_after_start'] else ''}
% for m in monitors:
% if m.params['en'].get_value() == 'True':
tb.${m.name}.start()
@@ -431,6 +470,7 @@ def main(top_block_cls=${class_name}, options=None):
% endfor
% endif
tb.wait()
+ ${'snippets_main_after_stop(tb)' if snippets['main_after_stop'] else ''}
% for m in monitors:
% if m.params['en'].get_value() == 'True':
tb.${m.name}.stop()
@@ -438,7 +478,6 @@ def main(top_block_cls=${class_name}, options=None):
% endfor
% endif
-
if __name__ == '__main__':
main()
% endif
diff --git a/grc/core/generator/top_block.py b/grc/core/generator/top_block.py
index 27d2428888..bf7ebcc224 100644
--- a/grc/core/generator/top_block.py
+++ b/grc/core/generator/top_block.py
@@ -191,7 +191,7 @@ class TopBlockGenerator(object):
blocks = [
b for b in fg.blocks
- if b.enabled and not (b.get_bypassed() or b.is_import or b in parameters or b.key == 'options')
+ if b.enabled and not (b.get_bypassed() or b.is_import or b.is_snippet or b in parameters or b.key == 'options')
]
blocks = expr_utils.sort_objects(blocks, operator.attrgetter('name'), _get_block_sort_text)
diff --git a/grc/core/params/param.py b/grc/core/params/param.py
index 816588efe2..ef8d7df291 100644
--- a/grc/core/params/param.py
+++ b/grc/core/params/param.py
@@ -245,9 +245,13 @@ class Param(Element):
elif dtype in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'):
# Do not check if file/directory exists, that is a runtime issue
try:
- value = self.parent_flowgraph.evaluate(expr)
- if not isinstance(value, str):
- raise Exception()
+ # Do not evaluate multiline strings (code snippets or comments)
+ if dtype not in ['_multiline','_multiline_python_external']:
+ value = self.parent_flowgraph.evaluate(expr)
+ if not isinstance(value, str):
+ raise Exception()
+ else:
+ value = str(expr)
except Exception:
self._stringify_flag = True
value = str(expr)