diff options
Diffstat (limited to 'grc/src/platforms/base')
-rw-r--r-- | grc/src/platforms/base/Block.py | 237 | ||||
-rw-r--r-- | grc/src/platforms/base/Connection.py | 88 | ||||
-rw-r--r-- | grc/src/platforms/base/Constants.py.in | 44 | ||||
-rw-r--r-- | grc/src/platforms/base/Element.py | 93 | ||||
-rw-r--r-- | grc/src/platforms/base/FlowGraph.py | 231 | ||||
-rw-r--r-- | grc/src/platforms/base/Makefile.am | 47 | ||||
-rw-r--r-- | grc/src/platforms/base/Param.py | 218 | ||||
-rw-r--r-- | grc/src/platforms/base/Platform.py | 144 | ||||
-rw-r--r-- | grc/src/platforms/base/Port.py | 106 | ||||
-rw-r--r-- | grc/src/platforms/base/__init__.py | 1 |
10 files changed, 1209 insertions, 0 deletions
diff --git a/grc/src/platforms/base/Block.py b/grc/src/platforms/base/Block.py new file mode 100644 index 000000000..e3ef84d94 --- /dev/null +++ b/grc/src/platforms/base/Block.py @@ -0,0 +1,237 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion 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 program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from ... import utils +from ... utils import odict +from Element import Element +from Param import Param +from Port import Port + +from Cheetah.Template import Template +from UserDict import UserDict + +class TemplateArg(UserDict): + """ + A cheetah template argument created from a param. + The str of this class evaluates to the param's to code method. + The use of this class as a dictionary (enum only) will reveal the enum opts. + The eval method can return the param evaluated to a raw python data type. + """ + + def __init__(self, param): + UserDict.__init__(self) + self._param = param + if param.is_enum(): + for key in param.get_opt_keys(): + self[key] = str(param.get_opt(key)) + + def __str__(self): + return str(self._param.to_code()) + + def eval(self): + return self._param.evaluate() + +class Block(Element): + + def __init__(self, flow_graph, n): + """ + Make a new block from nested data. + @param flow graph the parent element + @param n the nested odict + @return block a new block + """ + #grab the data + name = n['name'] + key = n['key'] + category = utils.exists_or_else(n, 'category', '') + params = utils.listify(n, 'param') + sources = utils.listify(n, 'source') + sinks = utils.listify(n, 'sink') + #build the block + Element.__init__(self, flow_graph) + #store the data + self._name = name + self._key = key + self._category = category + #create the param objects + self._params = odict() + #add the id param + self._params['id'] = self.get_parent().get_parent().Param( + self, + { + 'name': 'ID', + 'key': 'id', + 'type': 'id', + } + ) + self._params['_enabled'] = self.get_parent().get_parent().Param( + self, + { + 'name': 'Enabled', + 'key': '_enabled', + 'type': 'raw', + 'value': 'True', + 'hide': 'all', + } + ) + for param in map(lambda n: self.get_parent().get_parent().Param(self, n), params): + key = param.get_key() + #test against repeated keys + try: assert(key not in self.get_param_keys()) + except AssertionError: self._exit_with_error('Key "%s" already exists in params'%key) + #store the param + self._params[key] = param + #create the source objects + self._sources = odict() + for source in map(lambda n: self.get_parent().get_parent().Source(self, n), sources): + key = source.get_key() + #test against repeated keys + try: assert(key not in self.get_source_keys()) + except AssertionError: self._exit_with_error('Key "%s" already exists in sources'%key) + #store the port + self._sources[key] = source + #create the sink objects + self._sinks = odict() + for sink in map(lambda n: self.get_parent().get_parent().Sink(self, n), sinks): + key = sink.get_key() + #test against repeated keys + try: assert(key not in self.get_sink_keys()) + except AssertionError: self._exit_with_error('Key "%s" already exists in sinks'%key) + #store the port + self._sinks[key] = sink + #begin the testing + self.test() + + def test(self): + """ + Call test on all children. + """ + map(lambda c: c.test(), self.get_params() + self.get_sinks() + self.get_sources()) + + def get_enabled(self): + """ + Get the enabled state of the block. + @return true for enabled + """ + try: return eval(self.get_param('_enabled').get_value()) + except: return True + + def set_enabled(self, enabled): + """ + Set the enabled state of the block. + @param enabled true for enabled + """ + self.get_param('_enabled').set_value(str(enabled)) + + def validate(self): + """ + Validate the block. + All ports and params must be valid. + All checks must evaluate to true. + """ + if not self.get_enabled(): return + for c in self.get_params() + self.get_sinks() + self.get_sources(): + try: assert(c.is_valid()) + except AssertionError: + for msg in c.get_error_messages(): + self._add_error_message('%s: %s'%(c, msg)) + + def __str__(self): return 'Block - %s - %s(%s)'%(self.get_id(), self.get_name(), self.get_key()) + + def get_id(self): return self.get_param('id').get_value() + + def is_block(self): return True + + def get_doc(self): return self._doc + + def get_name(self): return self._name + + def get_key(self): return self._key + + def get_category(self): return self._category + + def get_doc(self): return '' + + def get_ports(self): return self.get_sources() + self.get_sinks() + + ############################################## + # Access Params + ############################################## + def get_param_keys(self): return self._params.keys() + def get_param(self, key): return self._params[key] + def get_params(self): return self._params.values() + + ############################################## + # Access Sinks + ############################################## + def get_sink_keys(self): return self._sinks.keys() + def get_sink(self, key): return self._sinks[key] + def get_sinks(self): return self._sinks.values() + + ############################################## + # Access Sources + ############################################## + def get_source_keys(self): return self._sources.keys() + def get_source(self, key): return self._sources[key] + def get_sources(self): return self._sources.values() + + def get_connections(self): + return sum([port.get_connections() for port in self.get_ports()], []) + + def resolve_dependencies(self, tmpl): + """ + Resolve a paramater dependency with cheetah templates. + @param tmpl the string with dependencies + @return the resolved value + """ + tmpl = str(tmpl) + if '$' not in tmpl: return tmpl + n = dict((p.get_key(), TemplateArg(p)) for p in self.get_params()) + try: return str(Template(tmpl, n)) + except Exception, e: return "-------->\n%s: %s\n<--------"%(e, tmpl) + + ############################################## + ## Import/Export Methods + ############################################## + def export_data(self): + """ + Export this block's params to nested data. + @return a nested data odict + """ + n = odict() + n['key'] = self.get_key() + n['param'] = map(lambda p: p.export_data(), self.get_params()) + return n + + def import_data(self, n): + """ + Import this block's params from nested data. + Any param keys that do not exist will be ignored. + @param n the nested data odict + """ + params_n = utils.listify(n, 'param') + for param_n in params_n: + #key and value must exist in the n data + if 'key' in param_n.keys() and 'value' in param_n.keys(): + key = param_n['key'] + value = param_n['value'] + #the key must exist in this block's params + if key in self.get_param_keys(): + self.get_param(key).set_value(value) + self.validate() diff --git a/grc/src/platforms/base/Connection.py b/grc/src/platforms/base/Connection.py new file mode 100644 index 000000000..3c0b42d78 --- /dev/null +++ b/grc/src/platforms/base/Connection.py @@ -0,0 +1,88 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion 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 program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from Element import Element +from ... utils import odict + +class Connection(Element): + + def __init__(self, flow_graph, porta, portb): + """ + Make a new connection given the parent and 2 ports. + @param flow_graph the parent of this element + @param porta a port (any direction) + @param portb a port (any direction) + @throws Error cannot make connection + @return a new connection + """ + Element.__init__(self, flow_graph) + source = sink = None + #separate the source and sink + for port in (porta, portb): + if port.is_source(): source = port + if port.is_sink(): sink = port + #verify the source and sink + assert(source and sink) + assert(not source.is_full()) + assert(not sink.is_full()) + self._source = source + self._sink = sink + + def __str__(self): return 'Connection (%s -> %s)'%(self.get_source(), self.get_sink()) + + def is_connection(self): return True + + def validate(self): + """ + Validate the connections. + The ports must match in type. + """ + source_type = self.get_source().get_type() + sink_type = self.get_sink().get_type() + try: assert source_type == sink_type + except AssertionError: self._add_error_message('Source type "%s" does not match sink type "%s".'%(source_type, sink_type)) + + def get_enabled(self): + """ + Get the enabled state of this connection. + @return true if source and sink blocks are enabled + """ + return self.get_source().get_parent().get_enabled() and \ + self.get_sink().get_parent().get_enabled() + + ############################# + # Access Ports + ############################# + def get_sink(self): return self._sink + def get_source(self): return self._source + + ############################################## + ## Import/Export Methods + ############################################## + def export_data(self): + """ + Export this connection's info. + @return a nested data odict + """ + n = odict() + n['source_block_id'] = self.get_source().get_parent().get_id() + n['sink_block_id'] = self.get_sink().get_parent().get_id() + n['source_key'] = self.get_source().get_key() + n['sink_key'] = self.get_sink().get_key() + return n diff --git a/grc/src/platforms/base/Constants.py.in b/grc/src/platforms/base/Constants.py.in new file mode 100644 index 000000000..26ba72d97 --- /dev/null +++ b/grc/src/platforms/base/Constants.py.in @@ -0,0 +1,44 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion 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 program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import os + +##The current version of this code +VERSION = '@VERSION@' + +##Location of external data files. +DATA_DIR = '@datadir@' + +##DTD validator for saved flow graphs. +FLOW_GRAPH_DTD = os.path.join(DATA_DIR, 'flow_graph.dtd') + +##The default file extension for flow graphs. +FLOW_GRAPH_FILE_EXTENSION = '.grc' + +##The default file extension for saving flow graph snap shots. +IMAGE_FILE_EXTENSION = '.png' + +##The default path for the open/save dialogs. +DEFAULT_FILE_PATH = os.getcwd() + +##The default icon for the gtk windows. +PY_GTK_ICON = os.path.join(DATA_DIR, 'grc-icon-256.png') + +##The users home directory. +HOME_DIR = os.path.expanduser('~') diff --git a/grc/src/platforms/base/Element.py b/grc/src/platforms/base/Element.py new file mode 100644 index 000000000..b6602a314 --- /dev/null +++ b/grc/src/platforms/base/Element.py @@ -0,0 +1,93 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion 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 program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +class Element(object): + + def __init__(self, parent=None): + self._parent = parent + self._error_messages = [] + self.flag() + + def test(self): + """ + Test the element against failures. + Overload this method in sub-classes. + """ + pass + + def validate(self): + """ + Validate the data in this element. + Set the error message non blank for errors. + Overload this method in sub-classes. + """ + pass + + def is_valid(self): + self._error_messages = []#reset err msgs + try: self.validate() + except: pass + return not self.get_error_messages() + + def _add_error_message(self, msg): + self._error_messages.append(msg) + + def get_error_messages(self): + return self._error_messages + + def get_parent(self): + return self._parent + + def _exit_with_error(self, error): + parent = self + #build hier list of elements + elements = list() + while(parent): + elements.insert(0, parent) + parent = parent.get_parent() + #build error string + err_str = ">>> Error:" + for i, element in enumerate(elements + [error]): + err_str = err_str + '\n' + ''.join(' '*(i+2)) + str(element) + err_str = err_str + '\n' + exit(err_str) + + ############################################## + ## Update flagging + ############################################## + def is_flagged(self): return self._flag + def flag(self): + self._flag = True + if self.get_parent(): self.get_parent().flag() + def deflag(self): + self._flag = False + if self.get_parent(): self.get_parent().deflag() + + ############################################## + ## Type testing methods + ############################################## + def is_element(self): return True + def is_platform(self): return False + def is_flow_graph(self): return False + def is_connection(self): return False + def is_block(self): return False + def is_source(self): return False + def is_sink(self): return False + def is_port(self): return False + def is_param(self): return False diff --git a/grc/src/platforms/base/FlowGraph.py b/grc/src/platforms/base/FlowGraph.py new file mode 100644 index 000000000..bb20c61d0 --- /dev/null +++ b/grc/src/platforms/base/FlowGraph.py @@ -0,0 +1,231 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion 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 program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from ... import utils +from ... utils import odict +from Element import Element +from Block import Block +from Connection import Connection +from ... gui import Messages + +class FlowGraph(Element): + + def __init__(self, platform): + """ + Make a flow graph from the arguments. + @param platform a platforms with blocks and contrcutors + @return the flow graph object + """ + #hold connections and blocks + self._elements = list() + #initialize + Element.__init__(self, platform) + #inital blank import + self.import_data({'flow_graph': {}}) + + def __str__(self): return 'FlowGraph - "%s"'%self.get_option('name') + + def get_option(self, key): + """ + Get the option for a given key. + The option comes from the special options block. + @param key the param key for the options block + @return the value held by that param + """ + return self._options_block.get_param(key).evaluate() + + def is_flow_graph(self): return True + + ############################################## + ## Access Elements + ############################################## + def get_block(self, id): return filter(lambda b: b.get_id() == id, self.get_blocks())[0] + def get_blocks(self): return filter(lambda e: e.is_block(), self.get_elements()) + def get_connections(self): return filter(lambda e: e.is_connection(), self.get_elements()) + def get_elements(self): + """ + Get a list of all the elements. + Always ensure that the options block is in the list. + @return the element list + """ + if self._options_block not in self._elements: self._elements.append(self._options_block) + #ensure uniqueness of the elements list + element_set = set() + element_list = list() + for element in self._elements: + if element not in element_set: element_list.append(element) + element_set.add(element) + #store cleaned up list + self._elements = element_list + return self._elements + + def get_enabled_blocks(self): + """ + Get a list of all blocks that are enabled. + @return a list of blocks + """ + return filter(lambda b: b.get_enabled(), self.get_blocks()) + + def get_enabled_connections(self): + """ + Get a list of all connections that are enabled. + @return a list of connections + """ + return filter(lambda c: c.get_enabled(), self.get_connections()) + + def get_new_block(self, key): + """ + Get a new block of the specified key. + Add the block to the list of elements. + @param key the block key + @return the new block or None if not found + """ + self.flag() + if key not in self.get_parent().get_block_keys(): return None + block = self.get_parent().get_new_block(self, key) + self.get_elements().append(block) + return block + + def connect(self, porta, portb): + """ + Create a connection between porta and portb. + @param porta a port + @param portb another port + @throw Exception bad connection + @return the new connection + """ + self.flag() + connection = self.get_parent().Connection(self, porta, portb) + self.get_elements().append(connection) + return connection + + def remove_element(self, element): + """ + Remove the element from the list of elements. + If the element is a port, remove the whole block. + If the element is a block, remove its connections. + If the element is a connection, just remove the connection. + """ + self.flag() + if element not in self.get_elements(): return + #found a port, set to parent signal block + if element.is_port(): + element = element.get_parent() + #remove block, remove all involved connections + if element.is_block(): + for port in element.get_ports(): + map(lambda c: self.remove_element(c), port.get_connections()) + #remove a connection + elif element.is_connection(): pass + self.get_elements().remove(element) + + def evaluate(self, expr): + """ + Evaluate the expression. + @param expr the string expression + @throw NotImplementedError + """ + raise NotImplementedError + + def validate(self): + """ + Validate the flow graph. + All connections and blocks must be valid. + """ + for c in self.get_elements(): + try: assert(c.is_valid()) + except AssertionError: self._add_error_message('Element "%s" is not valid.'%c) + + ############################################## + ## Import/Export Methods + ############################################## + def export_data(self): + """ + Export this flow graph to nested data. + Export all block and connection data. + @return a nested data odict + """ + import time + n = odict() + n['timestamp'] = time.ctime() + n['block'] = [block.export_data() for block in self.get_blocks()] + n['connection'] = [connection.export_data() for connection in self.get_connections()] + return {'flow_graph': n} + + def import_data(self, n): + """ + Import blocks and connections into this flow graph. + Clear this flowgraph of all previous blocks and connections. + Any blocks or connections in error will be ignored. + @param n the nested data odict + """ + #remove previous elements + self._elements = list() + #the flow graph tag must exists, or use blank data + if 'flow_graph' in n.keys(): fg_n = n['flow_graph'] + else: + Messages.send_error_load('Flow graph data not found, loading blank flow graph.') + fg_n = {} + blocks_n = utils.listify(fg_n, 'block') + connections_n = utils.listify(fg_n, 'connection') + #create option block + self._options_block = self.get_parent().get_new_block(self, 'options') + self._options_block.get_param('id').set_value('options') + #build the blocks + for block_n in blocks_n: + key = block_n['key'] + if key == 'options': block = self._options_block + else: block = self.get_new_block(key) + #only load the block when the block key was valid + if block: block.import_data(block_n) + else: Messages.send_error_load('Block key "%s" not found in %s'%(key, self.get_parent())) + #build the connections + for connection_n in connections_n: + #test that the data tags exist + try: + assert('source_block_id' in connection_n.keys()) + assert('sink_block_id' in connection_n.keys()) + assert('source_key' in connection_n.keys()) + assert('sink_key' in connection_n.keys()) + except AssertionError: continue + #try to make the connection + try: + #get the block ids + source_block_id = connection_n['source_block_id'] + sink_block_id = connection_n['sink_block_id'] + #get the port keys + source_key = connection_n['source_key'] + sink_key = connection_n['sink_key'] + #verify the blocks + block_ids = map(lambda b: b.get_id(), self.get_blocks()) + assert(source_block_id in block_ids) + assert(sink_block_id in block_ids) + #get the blocks + source_block = self.get_block(source_block_id) + sink_block = self.get_block(sink_block_id) + #verify the ports + assert(source_key in source_block.get_source_keys()) + assert(sink_key in sink_block.get_sink_keys()) + #get the ports + source = source_block.get_source(source_key) + sink = sink_block.get_sink(sink_key) + #build the connection + self.connect(source, sink) + except AssertionError: Messages.send_error_load('Connection between %s(%s) and %s(%s) could not be made.'%(source_block_id, source_key, sink_block_id, sink_key)) + self.validate() diff --git a/grc/src/platforms/base/Makefile.am b/grc/src/platforms/base/Makefile.am new file mode 100644 index 000000000..dca53b8b5 --- /dev/null +++ b/grc/src/platforms/base/Makefile.am @@ -0,0 +1,47 @@ +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +include $(top_srcdir)/grc/Makefile.inc + +ourpythondir = $(grc_src_prefix)/platforms/base + +ourpython_PYTHON = \ + Block.py \ + Connection.py \ + Constants.py \ + Element.py \ + FlowGraph.py \ + Param.py \ + Platform.py \ + Port.py \ + __init__.py + +BUILT_SOURCES = Constants.py + +Constants.py: Makefile $(srcdir)/Constants.py.in + sed \ + -e 's|@VERSION[@]|$(VERSION)|g' \ + -e 's|@datadir[@]|$(grc_base_data_dir)|g' \ + $(srcdir)/Constants.py.in > $@ + +EXTRA_DIST = Constants.py.in + +MOSTLYCLEANFILES = $(BUILT_SOURCES) diff --git a/grc/src/platforms/base/Param.py b/grc/src/platforms/base/Param.py new file mode 100644 index 000000000..3a8d98c30 --- /dev/null +++ b/grc/src/platforms/base/Param.py @@ -0,0 +1,218 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion 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 program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from ... import utils +from ... utils import odict +from Element import Element + +class Option(Element): + + def __init__(self, param, name, key, opts): + Element.__init__(self, param) + self._name = name + self._key = key + self._opts = dict() + for opt in opts: + #separate the key:value + try: key, value = opt.split(':') + except: self._exit_with_error('Error separating "%s" into key:value'%opt) + #test against repeated keys + try: assert(not self._opts.has_key(key)) + except AssertionError: self._exit_with_error('Key "%s" already exists in option'%key) + #store the option + self._opts[key] = value + + def __str__(self): return 'Option %s(%s)'%(self.get_name(), self.get_key()) + + def get_name(self): return self._name + + def get_key(self): return self._key + + ############################################## + # Access Opts + ############################################## + def get_opt_keys(self): return self._opts.keys() + def get_opt(self, key): return self._opts[key] + def get_opts(self): return self._opts.values() + + ############################################## + ## Static Make Methods + ############################################## + def make_option_from_n(param, n): + """ + Make a new option from nested data. + @param param the parent element + @param n the nested odict + @return a new option + """ + #grab the data + name = n['name'] + key = n['key'] + opts = utils.listify(n, 'opt') + #build the option + return Option( + param=param, + name=name, + key=key, + opts=opts, + ) + make_option_from_n = staticmethod(make_option_from_n) + +class Param(Element): + + ##possible param types + TYPES = ['enum', 'raw'] + + def __init__(self, block, n): + """ + Make a new param from nested data. + @param block the parent element + @param n the nested odict + @return a new param + """ + #grab the data + name = n['name'] + key = n['key'] + value = utils.exists_or_else(n, 'value', '') + type = n['type'] + hide = utils.exists_or_else(n, 'hide', '') + options = utils.listify(n, 'option') + #build the param + Element.__init__(self, block) + self._name = name + self._key = key + self._type = type + self._hide = hide + #create the Option objects from the n data + self._options = odict() + for option in map(lambda o: Option.make_option_from_n(self, o), options): + key = option.get_key() + #test against repeated keys + try: assert(key not in self.get_option_keys()) + except AssertionError: self._exit_with_error('Key "%s" already exists in options'%key) + #store the option + self._options[key] = option + #test the enum options + if self._options or self.is_enum(): + #test against bad combos of type and enum + try: assert(self._options) + except AssertionError: self._exit_with_error('At least one option must exist when type "enum" is set.') + try: assert(self.is_enum()) + except AssertionError: self._exit_with_error('Type "enum" must be set when options are present.') + #test against options with identical keys + try: assert(len(set(self.get_option_keys())) == len(self._options)) + except AssertionError: self._exit_with_error('Options keys "%s" are not unique.'%self.get_option_keys()) + #test against inconsistent keys in options + opt_keys = self._options.values()[0].get_opt_keys() + for option in self._options.values(): + try: assert(set(opt_keys) == set(option.get_opt_keys())) + except AssertionError: self._exit_with_error('Opt keys "%s" are not identical across all options.'%opt_keys) + #if a value is specified, it must be in the options keys + self._value = value or self.get_option_keys()[0] + try: assert(self.get_value() in self.get_option_keys()) + except AssertionError: self._exit_with_error('The value "%s" is not in the possible values of "%s".'%(self.get_value(), self.get_option_keys())) + else: self._value = value or '' + + def test(self): + """ + call test on all children + """ + map(lambda c: c.test(), self.get_options()) + + def validate(self): + """ + Validate the param. + The value must be evaluated and type must a possible type. + """ + try: + assert(self.get_type() in self.TYPES) + try: self.evaluate() + except: + #if the evaluate failed but added no error messages, add the generic one below + if not self.get_error_messages(): + self._add_error_message('Value "%s" cannot be evaluated.'%self.get_value()) + except AssertionError: self._add_error_message('Type "%s" is not a possible type.'%self.get_type()) + + def evaluate(self): + """ + Evaluate the value of this param. + @throw NotImplementedError + """ + raise NotImplementedError + + def to_code(self): + """ + Convert the value to code. + @throw NotImplementedError + """ + raise NotImplementedError + + def __str__(self): return 'Param - %s(%s)'%(self.get_name(), self.get_key()) + + def is_param(self): return True + + def get_name(self): return self._name + + def get_key(self): return self._key + + def get_hide(self): return self.get_parent().resolve_dependencies(self._hide) + + def get_value(self): + value = self._value + if self.is_enum() and value not in self.get_option_keys(): + value = self.get_option_keys()[0] + self.set_value(value) + return value + + def set_value(self, value): + self.flag() + self._value = str(value) #must be a string + + def get_type(self): return self.get_parent().resolve_dependencies(self._type) + + def is_enum(self): return self._type == 'enum' + + def is_type_dependent(self): return '$' in self._type + + ############################################## + # Access Options + ############################################## + def get_option_keys(self): return self._options.keys() + def get_option(self, key): return self._options[key] + def get_options(self): return self._options.values() + + ############################################## + # Access Opts + ############################################## + def get_opt_keys(self): return self._options[self.get_value()].get_opt_keys() + def get_opt(self, key): return self._options[self.get_value()].get_opt(key) + def get_opts(self): return self._options[self.get_value()].get_opts() + + ############################################## + ## Import/Export Methods + ############################################## + def export_data(self): + """ + Export this param's key/value. + @return a nested data odict + """ + n = odict() + n['key'] = self.get_key() + n['value'] = self.get_value() + return n diff --git a/grc/src/platforms/base/Platform.py b/grc/src/platforms/base/Platform.py new file mode 100644 index 000000000..c25b4a050 --- /dev/null +++ b/grc/src/platforms/base/Platform.py @@ -0,0 +1,144 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion 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 program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +import os +from ... utils import ParseXML +from ... import utils +from Element import Element as _Element +from FlowGraph import FlowGraph as _FlowGraph +from Connection import Connection as _Connection +from Block import Block as _Block +from Port import Port as _Port +from Param import Param as _Param +from Constants import DATA_DIR + +class Platform(_Element): + + def __init__(self, name, key, block_paths, block_dtd, block_tree, default_flow_graph, generator): + """ + Make a platform from the arguments. + @param name the platform name + @param key the unique platform key + @param block_paths the file paths to blocks in this platform + @param block_dtd the dtd validator for xml block wrappers + @param block_tree the nested tree of block keys and categories + @param default_flow_graph the default flow graph file path + @param load_one a single file to load into this platform or None + @return a platform object + """ + _Element.__init__(self) + self._name = name + self._key = key + self._block_paths = block_paths + self._block_dtd = block_dtd + self._block_tree = block_tree + self._default_flow_graph = default_flow_graph + self._generator = generator + #create a dummy flow graph for the blocks + self._flow_graph = _Element(self) + #load the blocks + self._blocks = dict() + self._blocks_n = dict() + for block_path in self._block_paths: + if os.path.isfile(block_path): self._load_block(block_path) + elif os.path.isdir(block_path): + for dirpath, dirnames, filenames in os.walk(block_path): + for filename in filter(lambda f: f.endswith('.xml'), filenames): + self._load_block(os.path.join(dirpath, filename)) + + def get_prefs_block(self): return self.get_new_flow_graph().get_new_block('preferences') + + def _load_block(self, f): + """ + Load the block wrapper from the file path. + The block wrapper must pass validation, and have a unique block key. + If any of the checks fail, exit with error. + @param f the file path + """ + try: ParseXML.validate_dtd(f, self._block_dtd) + except ParseXML.XMLSyntaxError, e: self._exit_with_error('Block definition "%s" failed: \n\t%s'%(f, e)) + n = ParseXML.from_file(f)['block'] + block = self.Block(self._flow_graph, n) + key = block.get_key() + #test against repeated keys + try: assert(key not in self.get_block_keys()) + except AssertionError: self._exit_with_error('Key "%s" already exists in blocks'%key) + #store the block + self._blocks[key] = block + self._blocks_n[key] = n + + def load_block_tree(self, block_tree): + """ + Load a block tree with categories and blocks. + Step 1: Load all blocks from the xml specification. + Step 2: Load blocks with builtin category specifications. + @param block_tree the block tree object + """ + #recursive function to load categories and blocks + def load_category(cat_n, parent=''): + #add this category + parent = '%s/%s'%(parent, cat_n['name']) + block_tree.add_block(parent) + #recursive call to load sub categories + map(lambda c: load_category(c, parent), utils.listify(cat_n, 'cat')) + #add blocks in this category + for block_key in utils.listify(cat_n, 'block'): + block_tree.add_block(parent, self.get_block(block_key)) + #load the block tree + f = self._block_tree + try: ParseXML.validate_dtd(f, os.path.join(DATA_DIR, 'block_tree.dtd')) + except ParseXML.XMLSyntaxError, e: self._exit_with_error('Block tree "%s" failed: \n\t%s'%(f, e)) + #add all blocks in the tree + load_category(ParseXML.from_file(f)['cat']) + #add all other blocks, use the catgory + for block in self.get_blocks(): + #blocks with empty categories are in the xml block tree or hidden + if block.get_category(): block_tree.add_block(block.get_category(), block) + + def __str__(self): return 'Platform - %s(%s)'%(self.get_key(), self.get_name()) + + def is_platform(self): return True + + def get_new_flow_graph(self): return self.FlowGraph(self) + + def get_default_flow_graph(self): return self._default_flow_graph + + def get_generator(self): return self._generator + + ############################################## + # Access Blocks + ############################################## + def get_block_keys(self): return self._blocks.keys() + def get_block(self, key): return self._blocks[key] + def get_blocks(self): return self._blocks.values() + def get_new_block(self, flow_graph, key): return self.Block(flow_graph, n=self._blocks_n[key]) + + def get_name(self): return self._name + + def get_key(self): return self._key + + ############################################## + # Constructors + ############################################## + FlowGraph = _FlowGraph + Connection = _Connection + Block = _Block + Source = _Port + Sink = _Port + Param = _Param diff --git a/grc/src/platforms/base/Port.py b/grc/src/platforms/base/Port.py new file mode 100644 index 000000000..61134791c --- /dev/null +++ b/grc/src/platforms/base/Port.py @@ -0,0 +1,106 @@ +""" +Copyright 2008 Free Software Foundation, Inc. +This file is part of GNU Radio + +GNU Radio Companion 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 2 +of the License, or (at your option) any later version. + +GNU Radio Companion 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 program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +""" + +from ... import utils +from Element import Element + +class Port(Element): + + ##possible port types + TYPES = [] + + def __init__(self, block, n): + """ + Make a new port from nested data. + @param block the parent element + @param n the nested odict + @return a new port + """ + #grab the data + name = n['name'] + key = n['key'] + type = n['type'] + #build the port + Element.__init__(self, block) + self._name = name + self._key = key + self._type = type + + def validate(self): + """ + Validate the port. + The port must be non-empty and type must a possible type. + """ + try: assert(not self.is_empty()) + except AssertionError: self._add_error_message('is empty.') + try: assert(self.get_type() in self.TYPES) + except AssertionError: self._add_error_message('Type "%s" is not a possible type.'%self.get_type()) + + def __str__(self): + if self.is_source(): + return 'Source - %s(%s)'%(self.get_name(), self.get_key()) + if self.is_sink(): + return 'Sink - %s(%s)'%(self.get_name(), self.get_key()) + + def is_port(self): return True + + def get_color(self): return '#FFFFFF' + + def get_name(self): return self._name + + def get_key(self): return self._key + + def is_sink(self): return self in self.get_parent().get_sinks() + + def is_source(self): return self in self.get_parent().get_sources() + + def get_type(self): return self.get_parent().resolve_dependencies(self._type) + + def get_connections(self): + """ + Get all connections that use this port. + @return a list of connection objects + """ + connections = self.get_parent().get_parent().get_connections() + connections = filter(lambda c: c.get_source() is self or c.get_sink() is self, connections) + return connections + + def is_connected(self): + """ + Is this port connected? + @return true if at least one connection + """ + return bool(self.get_connections()) + + def is_full(self): + """ + Is this port full of connections? + Generally a sink can handle one connection and a source can handle many. + @return true if the port is full + """ + if self.is_source(): return False + if self.is_sink(): return bool(self.get_connections()) + + def is_empty(self): + """ + Is this port empty? + An empty port has no connections. + @return true if empty + """ + return not self.get_connections() diff --git a/grc/src/platforms/base/__init__.py b/grc/src/platforms/base/__init__.py new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/grc/src/platforms/base/__init__.py @@ -0,0 +1 @@ + |