diff options
author | Sebastian Koslowski <sebastian.koslowski@gmail.com> | 2016-05-03 17:13:08 +0200 |
---|---|---|
committer | Johnathan Corgan <johnathan@corganlabs.com> | 2017-06-29 09:16:49 -0700 |
commit | 7f7fa2f91467fdb2b11312be8562e7b51fdeb199 (patch) | |
tree | 24268bac15b9920d2a15ddbb45eaf3b9b03718a1 | |
parent | 44cae388881821942e691a4d69a923bbd8d347db (diff) |
grc: added yaml/mako support
Includes basic converter from XML/Cheetah to YAML/Mako based block format.
100 files changed, 7134 insertions, 4507 deletions
diff --git a/gr-digital/examples/demod/pam_timing.grc b/gr-digital/examples/demod/pam_timing.grc index cb1f1ad981..3c0fcfeda0 100644 --- a/gr-digital/examples/demod/pam_timing.grc +++ b/gr-digital/examples/demod/pam_timing.grc @@ -1,22 +1,23 @@ -<?xml version='1.0' encoding='ASCII'?> +<?xml version='1.0' encoding='utf-8'?> +<?grc format='1' created='3.7.10'?> <flow_graph> <timestamp>Sat Jul 12 13:50:56 2014</timestamp> <block> <key>options</key> <param> - <key>id</key> - <value>pam_timing</value> + <key>author</key> + <value></value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>window_size</key> + <value>1280, 1024</value> </param> <param> - <key>title</key> - <value></value> + <key>category</key> + <value>Custom</value> </param> <param> - <key>author</key> + <key>comment</key> <value></value> </param> <param> @@ -24,16 +25,44 @@ <value></value> </param> <param> - <key>window_size</key> - <value>1280, 1024</value> + <key>_enabled</key> + <value>True</value> + </param> + <param> + <key>_coordinate</key> + <value>(-1, 0)</value> + </param> + <param> + <key>_rotation</key> + <value>0</value> </param> <param> <key>generate_options</key> <value>qt_gui</value> </param> <param> - <key>category</key> - <value>Custom</value> + <key>hier_block_src_path</key> + <value>.:</value> + </param> + <param> + <key>id</key> + <value>pam_timing</value> + </param> + <param> + <key>max_nouts</key> + <value>0</value> + </param> + <param> + <key>qt_qss_theme</key> + <value></value> + </param> + <param> + <key>realtime_scheduling</key> + <value></value> + </param> + <param> + <key>run_command</key> + <value>{python} -u {filename}</value> </param> <param> <key>run_options</key> @@ -44,233 +73,445 @@ <value>True</value> </param> <param> - <key>max_nouts</key> - <value>0</value> + <key>thread_safe_setters</key> + <value></value> </param> <param> - <key>realtime_scheduling</key> + <key>title</key> <value></value> </param> + </block> + <block> + <key>variable</key> <param> - <key>alias</key> + <key>comment</key> <value></value> </param> <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> <key>_coordinate</key> - <value>(-1, 0)</value> + <value>(206, 116)</value> </param> <param> <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>variable</key> <param> <key>id</key> <value>const</value> </param> <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> <key>value</key> <value>digital.qpsk_constellation()</value> </param> + </block> + <block> + <key>variable_qtgui_range</key> <param> - <key>alias</key> + <key>comment</key> <value></value> </param> <param> + <key>value</key> + <value>0</value> + </param> + <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> <key>_coordinate</key> - <value>(206, 116)</value> + <value>(268, 527)</value> + </param> + <param> + <key>gui_hint</key> + <value>4,2,1,1</value> </param> <param> <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>variable</key> <param> <key>id</key> - <value>sig_amp</value> + <value>freq_offset</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>label</key> + <value>Frequency Offset</value> + </param> + <param> + <key>min_len</key> + <value>200</value> + </param> + <param> + <key>orient</key> + <value>Qt.Horizontal</value> + </param> + <param> + <key>start</key> + <value>-.5</value> + </param> + <param> + <key>step</key> + <value>.01</value> + </param> + <param> + <key>stop</key> + <value>.5</value> + </param> + <param> + <key>rangeType</key> + <value>float</value> + </param> + <param> + <key>widget</key> + <value>counter_slider</value> + </param> + </block> + <block> + <key>variable_qtgui_range</key> + <param> + <key>comment</key> + <value></value> </param> <param> <key>value</key> <value>1</value> </param> <param> - <key>alias</key> - <value></value> + <key>_enabled</key> + <value>True</value> </param> <param> <key>_coordinate</key> - <value>(791, 46)</value> + <value>(6, 526)</value> + </param> + <param> + <key>gui_hint</key> + <value>2,2,1,1</value> </param> <param> <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>variable</key> <param> <key>id</key> - <value>samp_rate</value> + <value>interpratio</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>label</key> + <value>Timing Offset</value> </param> <param> - <key>value</key> - <value>32000</value> + <key>min_len</key> + <value>200</value> </param> <param> - <key>alias</key> - <value></value> + <key>orient</key> + <value>Qt.Horizontal</value> </param> <param> - <key>_coordinate</key> - <value>(267, 357)</value> + <key>start</key> + <value>.99</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>step</key> + <value>0.0001</value> + </param> + <param> + <key>stop</key> + <value>1.01</value> + </param> + <param> + <key>rangeType</key> + <value>float</value> + </param> + <param> + <key>widget</key> + <value>counter_slider</value> </param> </block> <block> <key>variable</key> <param> - <key>id</key> - <value>spb</value> + <key>comment</key> + <value></value> </param> <param> <key>_enabled</key> <value>True</value> </param> <param> + <key>_coordinate</key> + <value>(562, 334)</value> + </param> + <param> + <key>_rotation</key> + <value>0</value> + </param> + <param> + <key>id</key> + <value>nfilts</value> + </param> + <param> <key>value</key> - <value>4.2563</value> + <value>32</value> </param> + </block> + <block> + <key>variable_qtgui_range</key> <param> - <key>alias</key> + <key>comment</key> <value></value> </param> <param> + <key>value</key> + <value>0</value> + </param> + <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> <key>_coordinate</key> - <value>(300, 0)</value> + <value>(130, 528)</value> + </param> + <param> + <key>gui_hint</key> + <value>3,2,1,1</value> </param> <param> <key>_rotation</key> <value>0</value> </param> + <param> + <key>id</key> + <value>noise_amp</value> + </param> + <param> + <key>label</key> + <value>Channel Noise</value> + </param> + <param> + <key>min_len</key> + <value>200</value> + </param> + <param> + <key>orient</key> + <value>Qt.Horizontal</value> + </param> + <param> + <key>start</key> + <value>0</value> + </param> + <param> + <key>step</key> + <value>.001</value> + </param> + <param> + <key>stop</key> + <value>1</value> + </param> + <param> + <key>rangeType</key> + <value>float</value> + </param> + <param> + <key>widget</key> + <value>counter_slider</value> + </param> </block> <block> <key>variable</key> <param> - <key>id</key> - <value>rolloff</value> + <key>comment</key> + <value></value> </param> <param> <key>_enabled</key> <value>True</value> </param> <param> + <key>_coordinate</key> + <value>(482, 335)</value> + </param> + <param> + <key>_rotation</key> + <value>0</value> + </param> + <param> + <key>id</key> + <value>rolloff</value> + </param> + <param> <key>value</key> <value>.35</value> </param> + </block> + <block> + <key>variable</key> <param> - <key>alias</key> + <key>comment</key> <value></value> </param> <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> <key>_coordinate</key> - <value>(482, 335)</value> + <value>(267, 357)</value> </param> <param> <key>_rotation</key> <value>0</value> </param> + <param> + <key>id</key> + <value>samp_rate</value> + </param> + <param> + <key>value</key> + <value>32000</value> + </param> </block> <block> <key>variable</key> <param> - <key>id</key> - <value>nfilts</value> + <key>comment</key> + <value></value> </param> <param> <key>_enabled</key> <value>True</value> </param> <param> + <key>_coordinate</key> + <value>(791, 46)</value> + </param> + <param> + <key>_rotation</key> + <value>0</value> + </param> + <param> + <key>id</key> + <value>sig_amp</value> + </param> + <param> <key>value</key> - <value>32</value> + <value>1</value> </param> + </block> + <block> + <key>variable</key> <param> - <key>alias</key> + <key>comment</key> <value></value> </param> <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> <key>_coordinate</key> - <value>(562, 334)</value> + <value>(300, 0)</value> </param> <param> <key>_rotation</key> <value>0</value> </param> + <param> + <key>id</key> + <value>spb</value> + </param> + <param> + <key>value</key> + <value>4.2563</value> + </param> </block> <block> - <key>analog_random_source_x</key> + <key>variable_qtgui_range</key> <param> - <key>id</key> - <value>analog_random_source_x</value> + <key>comment</key> + <value></value> + </param> + <param> + <key>value</key> + <value>0</value> </param> <param> <key>_enabled</key> <value>True</value> </param> <param> - <key>type</key> - <value>byte</value> + <key>_coordinate</key> + <value>(451, 0)</value> </param> <param> - <key>min</key> + <key>gui_hint</key> + <value>1,2,1,1</value> + </param> + <param> + <key>_rotation</key> <value>0</value> </param> <param> - <key>max</key> - <value>const.arity()</value> + <key>id</key> + <value>time_bw</value> </param> <param> - <key>num_samps</key> - <value>10000000</value> + <key>label</key> + <value>Timing Loop BW</value> </param> <param> - <key>repeat</key> - <value>True</value> + <key>min_len</key> + <value>200</value> </param> <param> + <key>orient</key> + <value>Qt.Horizontal</value> + </param> + <param> + <key>start</key> + <value>0</value> + </param> + <param> + <key>step</key> + <value>.001</value> + </param> + <param> + <key>stop</key> + <value>.1</value> + </param> + <param> + <key>rangeType</key> + <value>float</value> + </param> + <param> + <key>widget</key> + <value>counter_slider</value> + </param> + </block> + <block> + <key>analog_random_source_x</key> + <param> <key>alias</key> <value></value> </param> <param> - <key>affinity</key> + <key>comment</key> <value></value> </param> <param> - <key>minoutbuf</key> - <value>0</value> + <key>affinity</key> + <value></value> </param> <param> - <key>maxoutbuf</key> - <value>0</value> + <key>_enabled</key> + <value>True</value> </param> <param> <key>_coordinate</key> @@ -280,103 +521,107 @@ <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>digital_chunks_to_symbols_xx</key> <param> <key>id</key> - <value>digital_chunks_to_symbols_xx</value> + <value>analog_random_source_x</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>maxoutbuf</key> + <value>0</value> </param> <param> - <key>in_type</key> - <value>byte</value> + <key>max</key> + <value>const.arity()</value> </param> <param> - <key>out_type</key> - <value>complex</value> + <key>minoutbuf</key> + <value>0</value> </param> <param> - <key>symbol_table</key> - <value>const.points()</value> + <key>min</key> + <value>0</value> </param> <param> - <key>dimension</key> - <value>1</value> + <key>num_samps</key> + <value>10000000</value> </param> <param> - <key>num_ports</key> - <value>1</value> + <key>type</key> + <value>byte</value> </param> <param> + <key>repeat</key> + <value>True</value> + </param> + </block> + <block> + <key>blocks_multiply_const_vxx</key> + <param> <key>alias</key> <value></value> </param> <param> - <key>affinity</key> + <key>comment</key> <value></value> </param> <param> - <key>minoutbuf</key> - <value>0</value> + <key>const</key> + <value>sig_amp</value> </param> <param> - <key>maxoutbuf</key> - <value>0</value> + <key>affinity</key> + <value></value> + </param> + <param> + <key>_enabled</key> + <value>True</value> </param> <param> <key>_coordinate</key> - <value>(203, 178)</value> + <value>(760, 159)</value> </param> <param> <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>blocks_throttle</key> <param> <key>id</key> - <value>blocks_throttle_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> + <value>blocks_multiply_const_vxx_0</value> </param> <param> <key>type</key> <value>complex</value> </param> <param> - <key>samples_per_second</key> - <value>samp_rate</value> + <key>maxoutbuf</key> + <value>0</value> </param> <param> - <key>vlen</key> - <value>1</value> + <key>minoutbuf</key> + <value>0</value> </param> <param> - <key>ignoretag</key> - <value>True</value> + <key>vlen</key> + <value>1</value> </param> + </block> + <block> + <key>blocks_throttle</key> <param> <key>alias</key> <value></value> </param> <param> - <key>affinity</key> + <key>comment</key> <value></value> </param> <param> - <key>minoutbuf</key> - <value>0</value> + <key>affinity</key> + <value></value> </param> <param> - <key>maxoutbuf</key> - <value>0</value> + <key>_enabled</key> + <value>True</value> </param> <param> <key>_coordinate</key> @@ -386,43 +631,47 @@ <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>channels_channel_model</key> <param> <key>id</key> - <value>channels_channel_model_0</value> + <value>blocks_throttle_0</value> </param> <param> - <key>_enabled</key> + <key>ignoretag</key> <value>True</value> </param> <param> - <key>noise_voltage</key> - <value>noise_amp</value> + <key>maxoutbuf</key> + <value>0</value> </param> <param> - <key>freq_offset</key> - <value>freq_offset</value> + <key>minoutbuf</key> + <value>0</value> </param> <param> - <key>epsilon</key> - <value>interpratio</value> + <key>samples_per_second</key> + <value>samp_rate</value> </param> <param> - <key>taps</key> - <value>1.0</value> + <key>type</key> + <value>complex</value> </param> <param> - <key>seed</key> - <value>42</value> + <key>vlen</key> + <value>1</value> + </param> + </block> + <block> + <key>channels_channel_model</key> + <param> + <key>alias</key> + <value></value> </param> <param> <key>block_tags</key> <value>False</value> </param> <param> - <key>alias</key> + <key>comment</key> <value></value> </param> <param> @@ -430,12 +679,16 @@ <value></value> </param> <param> - <key>minoutbuf</key> - <value>0</value> + <key>_enabled</key> + <value>True</value> </param> <param> - <key>maxoutbuf</key> - <value>0</value> + <key>epsilon</key> + <value>interpratio</value> + </param> + <param> + <key>freq_offset</key> + <value>freq_offset</value> </param> <param> <key>_coordinate</key> @@ -445,43 +698,39 @@ <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>pfb_arb_resampler_xxx</key> <param> <key>id</key> - <value>pfb_arb_resampler_xxx_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> + <value>channels_channel_model_0</value> </param> <param> - <key>type</key> - <value>ccf</value> + <key>maxoutbuf</key> + <value>0</value> </param> <param> - <key>rrate</key> - <value>spb</value> + <key>minoutbuf</key> + <value>0</value> </param> <param> - <key>taps</key> - <value>firdes.root_raised_cosine(nfilts, nfilts, 1.0, rolloff, 44*nfilts)</value> + <key>noise_voltage</key> + <value>noise_amp</value> </param> <param> - <key>nfilts</key> - <value>32</value> + <key>seed</key> + <value>42</value> </param> <param> - <key>atten</key> - <value>100</value> + <key>taps</key> + <value>1.0</value> </param> + </block> + <block> + <key>digital_chunks_to_symbols_xx</key> <param> - <key>samp_delay</key> - <value>0</value> + <key>alias</key> + <value></value> </param> <param> - <key>alias</key> + <key>comment</key> <value></value> </param> <param> @@ -489,73 +738,58 @@ <value></value> </param> <param> - <key>minoutbuf</key> - <value>0</value> + <key>dimension</key> + <value>1</value> </param> <param> - <key>maxoutbuf</key> - <value>0</value> + <key>_enabled</key> + <value>True</value> </param> <param> <key>_coordinate</key> - <value>(458, 179)</value> + <value>(203, 178)</value> </param> <param> <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>import</key> <param> <key>id</key> - <value>import_0</value> - </param> - <param> - <key>_enabled</key> - <value>True</value> - </param> - <param> - <key>import</key> - <value>from gnuradio import digital</value> - </param> - <param> - <key>alias</key> - <value></value> + <value>digital_chunks_to_symbols_xx</value> </param> <param> - <key>_coordinate</key> - <value>(-1, 61)</value> + <key>in_type</key> + <value>byte</value> </param> <param> - <key>_rotation</key> + <key>maxoutbuf</key> <value>0</value> </param> - </block> - <block> - <key>blocks_multiply_const_vxx</key> <param> - <key>id</key> - <value>blocks_multiply_const_vxx_0</value> + <key>minoutbuf</key> + <value>0</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>num_ports</key> + <value>1</value> </param> <param> - <key>type</key> + <key>out_type</key> <value>complex</value> </param> <param> - <key>const</key> - <value>sig_amp</value> + <key>symbol_table</key> + <value>const.points()</value> </param> + </block> + <block> + <key>digital_pfb_clock_sync_xxx</key> <param> - <key>vlen</key> - <value>1</value> + <key>alias</key> + <value></value> </param> <param> - <key>alias</key> + <key>comment</key> <value></value> </param> <param> @@ -563,614 +797,616 @@ <value></value> </param> <param> - <key>minoutbuf</key> - <value>0</value> + <key>_enabled</key> + <value>True</value> </param> <param> - <key>maxoutbuf</key> - <value>0</value> + <key>filter_size</key> + <value>nfilts</value> </param> <param> <key>_coordinate</key> - <value>(760, 159)</value> + <value>(467, 403)</value> </param> <param> <key>_rotation</key> <value>0</value> </param> - </block> - <block> - <key>qtgui_time_sink_x</key> <param> <key>id</key> - <value>qtgui_time_sink_x_0</value> + <value>digital_pfb_clock_sync_xxx_0</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>init_phase</key> + <value>0</value> </param> <param> - <key>type</key> - <value>complex</value> + <key>loop_bw</key> + <value>time_bw</value> </param> <param> - <key>name</key> - <value>Error</value> + <key>maxoutbuf</key> + <value>0</value> </param> <param> - <key>ylabel</key> - <value>Amplitude</value> + <key>max_dev</key> + <value>1.5</value> </param> <param> - <key>yunit</key> - <value>""</value> + <key>minoutbuf</key> + <value>0</value> </param> <param> - <key>size</key> - <value>1024</value> + <key>osps</key> + <value>1</value> </param> <param> - <key>srate</key> - <value>samp_rate</value> + <key>sps</key> + <value>spb</value> </param> <param> - <key>grid</key> - <value>False</value> + <key>taps</key> + <value>firdes.root_raised_cosine(nfilts, nfilts*spb, 1.0, rolloff, 44*nfilts)</value> </param> <param> - <key>autoscale</key> - <value>False</value> + <key>type</key> + <value>ccf</value> </param> + </block> + <block> + <key>import</key> <param> - <key>ymin</key> - <value>-1</value> + <key>alias</key> + <value></value> </param> <param> - <key>ymax</key> - <value>1</value> + <key>comment</key> + <value></value> </param> <param> - <key>nconnections</key> - <value>1</value> + <key>_enabled</key> + <value>True</value> </param> <param> - <key>update_time</key> - <value>0.10</value> + <key>_coordinate</key> + <value>(-1, 61)</value> </param> <param> - <key>entags</key> - <value>True</value> + <key>_rotation</key> + <value>0</value> </param> <param> - <key>gui_hint</key> - <value>notebook@3</value> + <key>id</key> + <value>import_0</value> </param> <param> - <key>tr_mode</key> - <value>qtgui.TRIG_MODE_FREE</value> + <key>import</key> + <value>from gnuradio import digital</value> </param> + </block> + <block> + <key>qtgui_tab_widget</key> <param> - <key>tr_slope</key> - <value>qtgui.TRIG_SLOPE_POS</value> + <key>alias</key> + <value></value> </param> <param> - <key>tr_level</key> - <value>0.0</value> + <key>comment</key> + <value></value> </param> <param> - <key>tr_delay</key> - <value>0</value> + <key>_enabled</key> + <value>True</value> </param> <param> - <key>tr_chan</key> + <key>_coordinate</key> + <value>(485, 554)</value> + </param> + <param> + <key>gui_hint</key> + <value>1,1,5,1</value> + </param> + <param> + <key>_rotation</key> <value>0</value> </param> <param> - <key>tr_tag</key> - <value>""</value> + <key>id</key> + <value>notebook</value> + </param> + <param> + <key>label0</key> + <value>Error</value> </param> <param> <key>label1</key> - <value></value> + <value>Phase</value> </param> <param> - <key>width1</key> - <value>1</value> + <key>label10</key> + <value>Tab 10</value> </param> <param> - <key>color1</key> - <value>"blue"</value> + <key>label11</key> + <value>Tab 11</value> </param> <param> - <key>style1</key> - <value>1</value> + <key>label12</key> + <value>Tab 12</value> </param> <param> - <key>marker1</key> - <value>-1</value> + <key>label13</key> + <value>Tab 13</value> </param> <param> - <key>alpha1</key> - <value>1.0</value> + <key>label14</key> + <value>Tab 14</value> </param> <param> - <key>label2</key> - <value></value> + <key>label15</key> + <value>Tab 15</value> </param> <param> - <key>width2</key> - <value>1</value> + <key>label16</key> + <value>Tab 16</value> </param> <param> - <key>color2</key> - <value>"red"</value> + <key>label17</key> + <value>Tab 17</value> </param> <param> - <key>style2</key> - <value>1</value> + <key>label18</key> + <value>Tab 18</value> </param> <param> - <key>marker2</key> - <value>-1</value> + <key>label19</key> + <value>Tab 19</value> </param> <param> - <key>alpha2</key> - <value>1.0</value> + <key>label2</key> + <value>Freq</value> </param> <param> <key>label3</key> - <value></value> + <value>Resampled Signal</value> </param> <param> - <key>width3</key> - <value>1</value> + <key>label4</key> + <value>Tab 4</value> </param> <param> - <key>color3</key> - <value>"green"</value> + <key>label5</key> + <value>Tab 5</value> </param> <param> - <key>style3</key> - <value>1</value> + <key>label6</key> + <value>Tab 6</value> </param> <param> - <key>marker3</key> - <value>-1</value> + <key>label7</key> + <value>Tab 7</value> </param> <param> - <key>alpha3</key> - <value>1.0</value> + <key>label8</key> + <value>Tab 8</value> </param> <param> - <key>label4</key> + <key>label9</key> + <value>Tab 9</value> + </param> + <param> + <key>num_tabs</key> + <value>4</value> + </param> + </block> + <block> + <key>pfb_arb_resampler_xxx</key> + <param> + <key>alias</key> <value></value> </param> <param> - <key>width4</key> - <value>1</value> + <key>comment</key> + <value></value> </param> <param> - <key>color4</key> - <value>"black"</value> + <key>affinity</key> + <value></value> </param> <param> - <key>style4</key> - <value>1</value> + <key>_enabled</key> + <value>True</value> </param> <param> - <key>marker4</key> - <value>-1</value> + <key>_coordinate</key> + <value>(458, 179)</value> </param> <param> - <key>alpha4</key> - <value>1.0</value> + <key>_rotation</key> + <value>0</value> </param> <param> - <key>label5</key> - <value></value> + <key>id</key> + <value>pfb_arb_resampler_xxx_0</value> </param> <param> - <key>width5</key> - <value>1</value> + <key>maxoutbuf</key> + <value>0</value> </param> <param> - <key>color5</key> - <value>"cyan"</value> + <key>minoutbuf</key> + <value>0</value> </param> <param> - <key>style5</key> - <value>1</value> + <key>nfilts</key> + <value>32</value> </param> <param> - <key>marker5</key> - <value>-1</value> + <key>rrate</key> + <value>spb</value> </param> <param> - <key>alpha5</key> - <value>1.0</value> + <key>samp_delay</key> + <value>0</value> </param> <param> - <key>label6</key> - <value></value> + <key>atten</key> + <value>100</value> </param> <param> - <key>width6</key> - <value>1</value> + <key>taps</key> + <value>firdes.root_raised_cosine(nfilts, nfilts, 1.0, rolloff, 44*nfilts)</value> </param> <param> - <key>color6</key> - <value>"magenta"</value> + <key>type</key> + <value>ccf</value> </param> + </block> + <block> + <key>qtgui_time_sink_x</key> <param> - <key>style6</key> - <value>1</value> + <key>autoscale</key> + <value>False</value> </param> <param> - <key>marker6</key> - <value>-1</value> + <key>axislabels</key> + <value>True</value> </param> <param> - <key>alpha6</key> - <value>1.0</value> + <key>alias</key> + <value></value> </param> <param> - <key>label7</key> + <key>comment</key> <value></value> </param> <param> - <key>width7</key> - <value>1</value> + <key>ctrlpanel</key> + <value>False</value> </param> <param> - <key>color7</key> - <value>"yellow"</value> + <key>affinity</key> + <value></value> </param> <param> - <key>style7</key> - <value>1</value> + <key>entags</key> + <value>True</value> </param> <param> - <key>marker7</key> - <value>-1</value> + <key>_enabled</key> + <value>True</value> </param> <param> - <key>alpha7</key> - <value>1.0</value> + <key>_coordinate</key> + <value>(1020, 128)</value> </param> <param> - <key>label8</key> - <value></value> + <key>gui_hint</key> + <value>notebook@3</value> </param> <param> - <key>width8</key> - <value>1</value> + <key>_rotation</key> + <value>0</value> </param> <param> - <key>color8</key> - <value>"dark red"</value> + <key>grid</key> + <value>False</value> </param> <param> - <key>style8</key> - <value>1</value> + <key>id</key> + <value>qtgui_time_sink_x_0</value> </param> <param> - <key>marker8</key> - <value>-1</value> + <key>legend</key> + <value>True</value> </param> <param> - <key>alpha8</key> + <key>alpha1</key> <value>1.0</value> </param> <param> - <key>label9</key> - <value></value> + <key>color1</key> + <value>"blue"</value> </param> <param> - <key>width9</key> - <value>1</value> + <key>label1</key> + <value></value> </param> <param> - <key>color9</key> - <value>"dark green"</value> + <key>marker1</key> + <value>-1</value> </param> <param> - <key>style9</key> + <key>style1</key> <value>1</value> </param> <param> - <key>marker9</key> - <value>-1</value> + <key>width1</key> + <value>1</value> </param> <param> - <key>alpha9</key> + <key>alpha10</key> <value>1.0</value> </param> <param> - <key>label10</key> - <value></value> + <key>color10</key> + <value>"blue"</value> </param> <param> - <key>width10</key> - <value>1</value> + <key>label10</key> + <value></value> </param> <param> - <key>color10</key> - <value>"blue"</value> + <key>marker10</key> + <value>-1</value> </param> <param> <key>style10</key> <value>1</value> </param> <param> - <key>marker10</key> - <value>-1</value> + <key>width10</key> + <value>1</value> </param> <param> - <key>alpha10</key> + <key>alpha2</key> <value>1.0</value> </param> <param> - <key>alias</key> - <value></value> + <key>color2</key> + <value>"red"</value> </param> <param> - <key>affinity</key> + <key>label2</key> <value></value> </param> <param> - <key>_coordinate</key> - <value>(1020, 128)</value> + <key>marker2</key> + <value>-1</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>style2</key> + <value>1</value> </param> - </block> - <block> - <key>qtgui_tab_widget</key> <param> - <key>id</key> - <value>notebook</value> + <key>width2</key> + <value>1</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>alpha3</key> + <value>1.0</value> </param> <param> - <key>num_tabs</key> - <value>4</value> + <key>color3</key> + <value>"green"</value> </param> <param> - <key>label0</key> - <value>Error</value> + <key>label3</key> + <value></value> </param> <param> - <key>label1</key> - <value>Phase</value> + <key>marker3</key> + <value>-1</value> </param> <param> - <key>label2</key> - <value>Freq</value> + <key>style3</key> + <value>1</value> </param> <param> - <key>label3</key> - <value>Resampled Signal</value> + <key>width3</key> + <value>1</value> </param> <param> - <key>label4</key> - <value>Tab 4</value> + <key>alpha4</key> + <value>1.0</value> </param> <param> - <key>gui_hint</key> - <value>1,1,5,1</value> + <key>color4</key> + <value>"black"</value> </param> <param> - <key>alias</key> + <key>label4</key> <value></value> </param> <param> - <key>_coordinate</key> - <value>(485, 554)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> - </param> - </block> - <block> - <key>variable_qtgui_range</key> - <param> - <key>id</key> - <value>interpratio</value> + <key>marker4</key> + <value>-1</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>style4</key> + <value>1</value> </param> <param> - <key>label</key> - <value>Timing Offset</value> + <key>width4</key> + <value>1</value> </param> <param> - <key>value</key> - <value>1</value> + <key>alpha5</key> + <value>1.0</value> </param> <param> - <key>start</key> - <value>.99</value> + <key>color5</key> + <value>"cyan"</value> </param> <param> - <key>stop</key> - <value>1.01</value> + <key>label5</key> + <value></value> </param> <param> - <key>step</key> - <value>0.0001</value> + <key>marker5</key> + <value>-1</value> </param> <param> - <key>widget</key> - <value>counter_slider</value> + <key>style5</key> + <value>1</value> </param> <param> - <key>orient</key> - <value>Qt.Horizontal</value> + <key>width5</key> + <value>1</value> </param> <param> - <key>min_len</key> - <value>200</value> + <key>alpha6</key> + <value>1.0</value> </param> <param> - <key>gui_hint</key> - <value>2,2,1,1</value> + <key>color6</key> + <value>"magenta"</value> </param> <param> - <key>alias</key> + <key>label6</key> <value></value> </param> <param> - <key>_coordinate</key> - <value>(6, 526)</value> + <key>marker6</key> + <value>-1</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>style6</key> + <value>1</value> </param> - </block> - <block> - <key>variable_qtgui_range</key> <param> - <key>id</key> - <value>noise_amp</value> + <key>width6</key> + <value>1</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>alpha7</key> + <value>1.0</value> </param> <param> - <key>label</key> - <value>Channel Noise</value> + <key>color7</key> + <value>"yellow"</value> </param> <param> - <key>value</key> - <value>0</value> + <key>label7</key> + <value></value> </param> <param> - <key>start</key> - <value>0</value> + <key>marker7</key> + <value>-1</value> </param> <param> - <key>stop</key> + <key>style7</key> <value>1</value> </param> <param> - <key>step</key> - <value>.001</value> - </param> - <param> - <key>widget</key> - <value>counter_slider</value> + <key>width7</key> + <value>1</value> </param> <param> - <key>orient</key> - <value>Qt.Horizontal</value> + <key>alpha8</key> + <value>1.0</value> </param> <param> - <key>min_len</key> - <value>200</value> + <key>color8</key> + <value>"dark red"</value> </param> <param> - <key>gui_hint</key> - <value>3,2,1,1</value> + <key>label8</key> + <value></value> </param> <param> - <key>alias</key> - <value></value> + <key>marker8</key> + <value>-1</value> </param> <param> - <key>_coordinate</key> - <value>(130, 528)</value> + <key>style8</key> + <value>1</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>width8</key> + <value>1</value> </param> - </block> - <block> - <key>variable_qtgui_range</key> <param> - <key>id</key> - <value>freq_offset</value> + <key>alpha9</key> + <value>1.0</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>color9</key> + <value>"dark green"</value> </param> <param> - <key>label</key> - <value>Frequency Offset</value> + <key>label9</key> + <value></value> </param> <param> - <key>value</key> - <value>0</value> + <key>marker9</key> + <value>-1</value> </param> <param> - <key>start</key> - <value>-.5</value> + <key>style9</key> + <value>1</value> </param> <param> - <key>stop</key> - <value>.5</value> + <key>width9</key> + <value>1</value> </param> <param> - <key>step</key> - <value>.01</value> + <key>name</key> + <value>Error</value> </param> <param> - <key>widget</key> - <value>counter_slider</value> + <key>nconnections</key> + <value>1</value> </param> <param> - <key>orient</key> - <value>Qt.Horizontal</value> + <key>size</key> + <value>1024</value> </param> <param> - <key>min_len</key> - <value>200</value> + <key>srate</key> + <value>samp_rate</value> </param> <param> - <key>gui_hint</key> - <value>4,2,1,1</value> + <key>tr_chan</key> + <value>0</value> </param> <param> - <key>alias</key> - <value></value> + <key>tr_delay</key> + <value>0</value> </param> <param> - <key>_coordinate</key> - <value>(268, 527)</value> + <key>tr_level</key> + <value>0.0</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>tr_mode</key> + <value>qtgui.TRIG_MODE_FREE</value> </param> - </block> - <block> - <key>qtgui_time_sink_x</key> <param> - <key>id</key> - <value>qtgui_time_sink_x_1_1</value> + <key>tr_slope</key> + <value>qtgui.TRIG_SLOPE_POS</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>tr_tag</key> + <value>""</value> </param> <param> <key>type</key> - <value>float</value> + <value>complex</value> </param> <param> - <key>name</key> - <value>Phase</value> + <key>update_time</key> + <value>0.10</value> </param> <param> <key>ylabel</key> @@ -1181,343 +1417,359 @@ <value>""</value> </param> <param> - <key>size</key> - <value>1024</value> + <key>ymax</key> + <value>1</value> </param> <param> - <key>srate</key> - <value>samp_rate</value> + <key>ymin</key> + <value>-1</value> </param> + </block> + <block> + <key>qtgui_time_sink_x</key> <param> - <key>grid</key> + <key>autoscale</key> <value>False</value> </param> <param> - <key>autoscale</key> + <key>axislabels</key> <value>True</value> </param> <param> - <key>ymin</key> - <value>-1</value> + <key>alias</key> + <value></value> </param> <param> - <key>ymax</key> - <value>1</value> + <key>comment</key> + <value></value> </param> <param> - <key>nconnections</key> - <value>1</value> + <key>ctrlpanel</key> + <value>False</value> </param> <param> - <key>update_time</key> - <value>0.10</value> + <key>affinity</key> + <value></value> </param> <param> <key>entags</key> <value>True</value> </param> <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> + <key>_coordinate</key> + <value>(1029, 281)</value> + </param> + <param> <key>gui_hint</key> - <value>notebook@1</value> + <value></value> </param> <param> - <key>tr_mode</key> - <value>qtgui.TRIG_MODE_FREE</value> + <key>_rotation</key> + <value>0</value> </param> <param> - <key>tr_slope</key> - <value>qtgui.TRIG_SLOPE_POS</value> + <key>grid</key> + <value>False</value> </param> <param> - <key>tr_level</key> - <value>0.0</value> + <key>id</key> + <value>qtgui_time_sink_x_0_0</value> </param> <param> - <key>tr_delay</key> - <value>0</value> + <key>legend</key> + <value>True</value> </param> <param> - <key>tr_chan</key> - <value>0</value> + <key>alpha1</key> + <value>1.0</value> </param> <param> - <key>tr_tag</key> - <value>""</value> + <key>color1</key> + <value>"blue"</value> </param> <param> <key>label1</key> <value></value> </param> <param> + <key>marker1</key> + <value>-1</value> + </param> + <param> + <key>style1</key> + <value>1</value> + </param> + <param> <key>width1</key> <value>1</value> </param> <param> - <key>color1</key> + <key>alpha10</key> + <value>1.0</value> + </param> + <param> + <key>color10</key> <value>"blue"</value> </param> <param> - <key>style1</key> - <value>1</value> + <key>label10</key> + <value></value> </param> <param> - <key>marker1</key> + <key>marker10</key> <value>-1</value> </param> <param> - <key>alpha1</key> - <value>1.0</value> + <key>style10</key> + <value>1</value> </param> <param> - <key>label2</key> - <value></value> + <key>width10</key> + <value>1</value> </param> <param> - <key>width2</key> - <value>1</value> + <key>alpha2</key> + <value>1.0</value> </param> <param> <key>color2</key> <value>"red"</value> </param> <param> - <key>style2</key> - <value>1</value> + <key>label2</key> + <value></value> </param> <param> <key>marker2</key> <value>-1</value> </param> <param> - <key>alpha2</key> - <value>1.0</value> + <key>style2</key> + <value>1</value> </param> <param> - <key>label3</key> - <value></value> + <key>width2</key> + <value>1</value> </param> <param> - <key>width3</key> - <value>1</value> + <key>alpha3</key> + <value>1.0</value> </param> <param> <key>color3</key> <value>"green"</value> </param> <param> - <key>style3</key> - <value>1</value> + <key>label3</key> + <value></value> </param> <param> <key>marker3</key> <value>-1</value> </param> <param> - <key>alpha3</key> - <value>1.0</value> + <key>style3</key> + <value>1</value> </param> <param> - <key>label4</key> - <value></value> + <key>width3</key> + <value>1</value> </param> <param> - <key>width4</key> - <value>1</value> + <key>alpha4</key> + <value>1.0</value> </param> <param> <key>color4</key> <value>"black"</value> </param> <param> - <key>style4</key> - <value>1</value> + <key>label4</key> + <value></value> </param> <param> <key>marker4</key> <value>-1</value> </param> <param> - <key>alpha4</key> - <value>1.0</value> + <key>style4</key> + <value>1</value> </param> <param> - <key>label5</key> - <value></value> + <key>width4</key> + <value>1</value> </param> <param> - <key>width5</key> - <value>1</value> + <key>alpha5</key> + <value>1.0</value> </param> <param> <key>color5</key> <value>"cyan"</value> </param> <param> - <key>style5</key> - <value>1</value> + <key>label5</key> + <value></value> </param> <param> <key>marker5</key> <value>-1</value> </param> <param> - <key>alpha5</key> - <value>1.0</value> + <key>style5</key> + <value>1</value> </param> <param> - <key>label6</key> - <value></value> + <key>width5</key> + <value>1</value> </param> <param> - <key>width6</key> - <value>1</value> + <key>alpha6</key> + <value>1.0</value> </param> <param> <key>color6</key> <value>"magenta"</value> </param> <param> - <key>style6</key> - <value>1</value> + <key>label6</key> + <value></value> </param> <param> <key>marker6</key> <value>-1</value> </param> <param> - <key>alpha6</key> - <value>1.0</value> + <key>style6</key> + <value>1</value> </param> <param> - <key>label7</key> - <value></value> + <key>width6</key> + <value>1</value> </param> <param> - <key>width7</key> - <value>1</value> + <key>alpha7</key> + <value>1.0</value> </param> <param> <key>color7</key> <value>"yellow"</value> </param> <param> - <key>style7</key> - <value>1</value> + <key>label7</key> + <value></value> </param> <param> <key>marker7</key> <value>-1</value> </param> <param> - <key>alpha7</key> - <value>1.0</value> + <key>style7</key> + <value>1</value> </param> <param> - <key>label8</key> - <value></value> + <key>width7</key> + <value>1</value> </param> <param> - <key>width8</key> - <value>1</value> + <key>alpha8</key> + <value>1.0</value> </param> <param> <key>color8</key> <value>"dark red"</value> </param> <param> - <key>style8</key> - <value>1</value> + <key>label8</key> + <value></value> </param> <param> <key>marker8</key> <value>-1</value> </param> <param> - <key>alpha8</key> - <value>1.0</value> + <key>style8</key> + <value>1</value> </param> <param> - <key>label9</key> - <value></value> + <key>width8</key> + <value>1</value> </param> <param> - <key>width9</key> - <value>1</value> + <key>alpha9</key> + <value>1.0</value> </param> <param> <key>color9</key> <value>"dark green"</value> </param> <param> - <key>style9</key> - <value>1</value> + <key>label9</key> + <value></value> </param> <param> <key>marker9</key> <value>-1</value> </param> <param> - <key>alpha9</key> - <value>1.0</value> - </param> - <param> - <key>label10</key> - <value></value> + <key>style9</key> + <value>1</value> </param> <param> - <key>width10</key> + <key>width9</key> <value>1</value> </param> <param> - <key>color10</key> - <value>"blue"</value> + <key>name</key> + <value>Scope Plot</value> </param> <param> - <key>style10</key> + <key>nconnections</key> <value>1</value> </param> <param> - <key>marker10</key> - <value>-1</value> + <key>size</key> + <value>1024</value> </param> <param> - <key>alpha10</key> - <value>1.0</value> + <key>srate</key> + <value>samp_rate</value> </param> <param> - <key>alias</key> - <value></value> + <key>tr_chan</key> + <value>0</value> </param> <param> - <key>affinity</key> - <value></value> + <key>tr_delay</key> + <value>0</value> </param> <param> - <key>_coordinate</key> - <value>(1014, 622)</value> + <key>tr_level</key> + <value>0.0</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>tr_mode</key> + <value>qtgui.TRIG_MODE_FREE</value> </param> - </block> - <block> - <key>qtgui_time_sink_x</key> <param> - <key>id</key> - <value>qtgui_time_sink_x_1_0</value> + <key>tr_slope</key> + <value>qtgui.TRIG_SLOPE_POS</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>tr_tag</key> + <value>""</value> </param> <param> <key>type</key> - <value>float</value> + <value>complex</value> </param> <param> - <key>name</key> - <value>Rate</value> + <key>update_time</key> + <value>0.10</value> </param> <param> <key>ylabel</key> @@ -1528,343 +1780,359 @@ <value>""</value> </param> <param> - <key>size</key> - <value>1024</value> + <key>ymax</key> + <value>1</value> </param> <param> - <key>srate</key> - <value>samp_rate</value> + <key>ymin</key> + <value>-1</value> </param> + </block> + <block> + <key>qtgui_time_sink_x</key> <param> - <key>grid</key> - <value>False</value> + <key>autoscale</key> + <value>True</value> </param> <param> - <key>autoscale</key> + <key>axislabels</key> <value>True</value> </param> <param> - <key>ymin</key> - <value>-1</value> + <key>alias</key> + <value></value> </param> <param> - <key>ymax</key> - <value>1</value> + <key>comment</key> + <value></value> </param> <param> - <key>nconnections</key> - <value>1</value> + <key>ctrlpanel</key> + <value>False</value> </param> <param> - <key>update_time</key> - <value>0.10</value> + <key>affinity</key> + <value></value> </param> <param> <key>entags</key> <value>True</value> </param> <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> + <key>_coordinate</key> + <value>(1024, 400)</value> + </param> + <param> <key>gui_hint</key> - <value>notebook@2</value> + <value>notebook@0</value> </param> <param> - <key>tr_mode</key> - <value>qtgui.TRIG_MODE_FREE</value> + <key>_rotation</key> + <value>0</value> </param> <param> - <key>tr_slope</key> - <value>qtgui.TRIG_SLOPE_POS</value> + <key>grid</key> + <value>False</value> </param> <param> - <key>tr_level</key> - <value>0.0</value> + <key>id</key> + <value>qtgui_time_sink_x_1</value> </param> <param> - <key>tr_delay</key> - <value>0</value> + <key>legend</key> + <value>True</value> </param> <param> - <key>tr_chan</key> - <value>0</value> + <key>alpha1</key> + <value>1.0</value> </param> <param> - <key>tr_tag</key> - <value>""</value> + <key>color1</key> + <value>"blue"</value> </param> <param> <key>label1</key> <value></value> </param> <param> + <key>marker1</key> + <value>-1</value> + </param> + <param> + <key>style1</key> + <value>1</value> + </param> + <param> <key>width1</key> <value>1</value> </param> <param> - <key>color1</key> + <key>alpha10</key> + <value>1.0</value> + </param> + <param> + <key>color10</key> <value>"blue"</value> </param> <param> - <key>style1</key> - <value>1</value> + <key>label10</key> + <value></value> </param> <param> - <key>marker1</key> + <key>marker10</key> <value>-1</value> </param> <param> - <key>alpha1</key> - <value>1.0</value> + <key>style10</key> + <value>1</value> </param> <param> - <key>label2</key> - <value></value> + <key>width10</key> + <value>1</value> </param> <param> - <key>width2</key> - <value>1</value> + <key>alpha2</key> + <value>1.0</value> </param> <param> <key>color2</key> <value>"red"</value> </param> <param> - <key>style2</key> - <value>1</value> + <key>label2</key> + <value></value> </param> <param> <key>marker2</key> <value>-1</value> </param> <param> - <key>alpha2</key> - <value>1.0</value> + <key>style2</key> + <value>1</value> </param> <param> - <key>label3</key> - <value></value> + <key>width2</key> + <value>1</value> </param> <param> - <key>width3</key> - <value>1</value> + <key>alpha3</key> + <value>1.0</value> </param> <param> <key>color3</key> <value>"green"</value> </param> <param> - <key>style3</key> - <value>1</value> + <key>label3</key> + <value></value> </param> <param> <key>marker3</key> <value>-1</value> </param> <param> - <key>alpha3</key> - <value>1.0</value> + <key>style3</key> + <value>1</value> </param> <param> - <key>label4</key> - <value></value> + <key>width3</key> + <value>1</value> </param> <param> - <key>width4</key> - <value>1</value> + <key>alpha4</key> + <value>1.0</value> </param> <param> <key>color4</key> <value>"black"</value> </param> <param> - <key>style4</key> - <value>1</value> + <key>label4</key> + <value></value> </param> <param> <key>marker4</key> <value>-1</value> </param> <param> - <key>alpha4</key> - <value>1.0</value> + <key>style4</key> + <value>1</value> </param> <param> - <key>label5</key> - <value></value> + <key>width4</key> + <value>1</value> </param> <param> - <key>width5</key> - <value>1</value> + <key>alpha5</key> + <value>1.0</value> </param> <param> <key>color5</key> <value>"cyan"</value> </param> <param> - <key>style5</key> - <value>1</value> + <key>label5</key> + <value></value> </param> <param> <key>marker5</key> <value>-1</value> </param> <param> - <key>alpha5</key> - <value>1.0</value> + <key>style5</key> + <value>1</value> </param> <param> - <key>label6</key> - <value></value> + <key>width5</key> + <value>1</value> </param> <param> - <key>width6</key> - <value>1</value> + <key>alpha6</key> + <value>1.0</value> </param> <param> <key>color6</key> <value>"magenta"</value> </param> <param> - <key>style6</key> - <value>1</value> + <key>label6</key> + <value></value> </param> <param> <key>marker6</key> <value>-1</value> </param> <param> - <key>alpha6</key> - <value>1.0</value> + <key>style6</key> + <value>1</value> </param> <param> - <key>label7</key> - <value></value> + <key>width6</key> + <value>1</value> </param> <param> - <key>width7</key> - <value>1</value> + <key>alpha7</key> + <value>1.0</value> </param> <param> <key>color7</key> <value>"yellow"</value> </param> <param> - <key>style7</key> - <value>1</value> + <key>label7</key> + <value></value> </param> <param> <key>marker7</key> <value>-1</value> </param> <param> - <key>alpha7</key> - <value>1.0</value> + <key>style7</key> + <value>1</value> </param> <param> - <key>label8</key> - <value></value> + <key>width7</key> + <value>1</value> </param> <param> - <key>width8</key> - <value>1</value> + <key>alpha8</key> + <value>1.0</value> </param> <param> <key>color8</key> <value>"dark red"</value> </param> <param> - <key>style8</key> - <value>1</value> + <key>label8</key> + <value></value> </param> <param> <key>marker8</key> <value>-1</value> </param> <param> - <key>alpha8</key> - <value>1.0</value> + <key>style8</key> + <value>1</value> </param> <param> - <key>label9</key> - <value></value> + <key>width8</key> + <value>1</value> </param> <param> - <key>width9</key> - <value>1</value> + <key>alpha9</key> + <value>1.0</value> </param> <param> <key>color9</key> <value>"dark green"</value> </param> <param> - <key>style9</key> - <value>1</value> + <key>label9</key> + <value></value> </param> <param> <key>marker9</key> <value>-1</value> </param> <param> - <key>alpha9</key> - <value>1.0</value> - </param> - <param> - <key>label10</key> - <value></value> + <key>style9</key> + <value>1</value> </param> <param> - <key>width10</key> + <key>width9</key> <value>1</value> </param> <param> - <key>color10</key> - <value>"blue"</value> + <key>name</key> + <value>Error</value> </param> <param> - <key>style10</key> + <key>nconnections</key> <value>1</value> </param> <param> - <key>marker10</key> - <value>-1</value> + <key>size</key> + <value>1024</value> </param> <param> - <key>alpha10</key> - <value>1.0</value> + <key>srate</key> + <value>samp_rate</value> </param> <param> - <key>alias</key> - <value></value> + <key>tr_chan</key> + <value>0</value> </param> <param> - <key>affinity</key> - <value></value> + <key>tr_delay</key> + <value>0</value> </param> <param> - <key>_coordinate</key> - <value>(1018, 503)</value> + <key>tr_level</key> + <value>0.0</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>tr_mode</key> + <value>qtgui.TRIG_MODE_FREE</value> </param> - </block> - <block> - <key>qtgui_time_sink_x</key> <param> - <key>id</key> - <value>qtgui_time_sink_x_1</value> + <key>tr_slope</key> + <value>qtgui.TRIG_SLOPE_POS</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>tr_tag</key> + <value>""</value> </param> <param> <key>type</key> <value>float</value> </param> <param> - <key>name</key> - <value>Error</value> + <key>update_time</key> + <value>0.10</value> </param> <param> <key>ylabel</key> @@ -1875,343 +2143,359 @@ <value>""</value> </param> <param> - <key>size</key> - <value>1024</value> + <key>ymax</key> + <value>1</value> </param> <param> - <key>srate</key> - <value>samp_rate</value> + <key>ymin</key> + <value>-1</value> </param> + </block> + <block> + <key>qtgui_time_sink_x</key> <param> - <key>grid</key> - <value>False</value> + <key>autoscale</key> + <value>True</value> </param> <param> - <key>autoscale</key> + <key>axislabels</key> <value>True</value> </param> <param> - <key>ymin</key> - <value>-1</value> + <key>alias</key> + <value></value> </param> <param> - <key>ymax</key> - <value>1</value> + <key>comment</key> + <value></value> </param> <param> - <key>nconnections</key> - <value>1</value> + <key>ctrlpanel</key> + <value>False</value> </param> <param> - <key>update_time</key> - <value>0.10</value> + <key>affinity</key> + <value></value> </param> <param> <key>entags</key> <value>True</value> </param> <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> + <key>_coordinate</key> + <value>(1018, 503)</value> + </param> + <param> <key>gui_hint</key> - <value>notebook@0</value> + <value>notebook@2</value> </param> <param> - <key>tr_mode</key> - <value>qtgui.TRIG_MODE_FREE</value> + <key>_rotation</key> + <value>0</value> </param> <param> - <key>tr_slope</key> - <value>qtgui.TRIG_SLOPE_POS</value> + <key>grid</key> + <value>False</value> </param> <param> - <key>tr_level</key> - <value>0.0</value> + <key>id</key> + <value>qtgui_time_sink_x_1_0</value> </param> <param> - <key>tr_delay</key> - <value>0</value> + <key>legend</key> + <value>True</value> </param> <param> - <key>tr_chan</key> - <value>0</value> + <key>alpha1</key> + <value>1.0</value> </param> <param> - <key>tr_tag</key> - <value>""</value> + <key>color1</key> + <value>"blue"</value> </param> <param> <key>label1</key> <value></value> </param> <param> + <key>marker1</key> + <value>-1</value> + </param> + <param> + <key>style1</key> + <value>1</value> + </param> + <param> <key>width1</key> <value>1</value> </param> <param> - <key>color1</key> + <key>alpha10</key> + <value>1.0</value> + </param> + <param> + <key>color10</key> <value>"blue"</value> </param> <param> - <key>style1</key> - <value>1</value> + <key>label10</key> + <value></value> </param> <param> - <key>marker1</key> + <key>marker10</key> <value>-1</value> </param> <param> - <key>alpha1</key> - <value>1.0</value> + <key>style10</key> + <value>1</value> </param> <param> - <key>label2</key> - <value></value> + <key>width10</key> + <value>1</value> </param> <param> - <key>width2</key> - <value>1</value> + <key>alpha2</key> + <value>1.0</value> </param> <param> <key>color2</key> <value>"red"</value> </param> <param> - <key>style2</key> - <value>1</value> + <key>label2</key> + <value></value> </param> <param> <key>marker2</key> <value>-1</value> </param> <param> - <key>alpha2</key> - <value>1.0</value> + <key>style2</key> + <value>1</value> </param> <param> - <key>label3</key> - <value></value> + <key>width2</key> + <value>1</value> </param> <param> - <key>width3</key> - <value>1</value> + <key>alpha3</key> + <value>1.0</value> </param> <param> <key>color3</key> <value>"green"</value> </param> <param> - <key>style3</key> - <value>1</value> + <key>label3</key> + <value></value> </param> <param> <key>marker3</key> <value>-1</value> </param> <param> - <key>alpha3</key> - <value>1.0</value> + <key>style3</key> + <value>1</value> </param> <param> - <key>label4</key> - <value></value> + <key>width3</key> + <value>1</value> </param> <param> - <key>width4</key> - <value>1</value> + <key>alpha4</key> + <value>1.0</value> </param> <param> <key>color4</key> <value>"black"</value> </param> <param> - <key>style4</key> - <value>1</value> + <key>label4</key> + <value></value> </param> <param> <key>marker4</key> <value>-1</value> </param> <param> - <key>alpha4</key> - <value>1.0</value> + <key>style4</key> + <value>1</value> </param> <param> - <key>label5</key> - <value></value> + <key>width4</key> + <value>1</value> </param> <param> - <key>width5</key> - <value>1</value> + <key>alpha5</key> + <value>1.0</value> </param> <param> <key>color5</key> <value>"cyan"</value> </param> <param> - <key>style5</key> - <value>1</value> + <key>label5</key> + <value></value> </param> <param> <key>marker5</key> <value>-1</value> </param> <param> - <key>alpha5</key> - <value>1.0</value> + <key>style5</key> + <value>1</value> </param> <param> - <key>label6</key> - <value></value> + <key>width5</key> + <value>1</value> </param> <param> - <key>width6</key> - <value>1</value> + <key>alpha6</key> + <value>1.0</value> </param> <param> <key>color6</key> <value>"magenta"</value> </param> <param> - <key>style6</key> - <value>1</value> + <key>label6</key> + <value></value> </param> <param> <key>marker6</key> <value>-1</value> </param> <param> - <key>alpha6</key> - <value>1.0</value> + <key>style6</key> + <value>1</value> </param> <param> - <key>label7</key> - <value></value> + <key>width6</key> + <value>1</value> </param> <param> - <key>width7</key> - <value>1</value> + <key>alpha7</key> + <value>1.0</value> </param> <param> <key>color7</key> <value>"yellow"</value> </param> <param> - <key>style7</key> - <value>1</value> + <key>label7</key> + <value></value> </param> <param> <key>marker7</key> <value>-1</value> </param> <param> - <key>alpha7</key> - <value>1.0</value> + <key>style7</key> + <value>1</value> </param> <param> - <key>label8</key> - <value></value> + <key>width7</key> + <value>1</value> </param> <param> - <key>width8</key> - <value>1</value> + <key>alpha8</key> + <value>1.0</value> </param> <param> <key>color8</key> <value>"dark red"</value> </param> <param> - <key>style8</key> - <value>1</value> + <key>label8</key> + <value></value> </param> <param> <key>marker8</key> <value>-1</value> </param> <param> - <key>alpha8</key> - <value>1.0</value> + <key>style8</key> + <value>1</value> </param> <param> - <key>label9</key> - <value></value> + <key>width8</key> + <value>1</value> </param> <param> - <key>width9</key> - <value>1</value> + <key>alpha9</key> + <value>1.0</value> </param> <param> <key>color9</key> <value>"dark green"</value> </param> <param> - <key>style9</key> - <value>1</value> + <key>label9</key> + <value></value> </param> <param> <key>marker9</key> <value>-1</value> </param> <param> - <key>alpha9</key> - <value>1.0</value> - </param> - <param> - <key>label10</key> - <value></value> + <key>style9</key> + <value>1</value> </param> <param> - <key>width10</key> + <key>width9</key> <value>1</value> </param> <param> - <key>color10</key> - <value>"blue"</value> + <key>name</key> + <value>Rate</value> </param> <param> - <key>style10</key> + <key>nconnections</key> <value>1</value> </param> <param> - <key>marker10</key> - <value>-1</value> + <key>size</key> + <value>1024</value> </param> <param> - <key>alpha10</key> - <value>1.0</value> + <key>srate</key> + <value>samp_rate</value> </param> <param> - <key>alias</key> - <value></value> + <key>tr_chan</key> + <value>0</value> </param> <param> - <key>affinity</key> - <value></value> + <key>tr_delay</key> + <value>0</value> </param> <param> - <key>_coordinate</key> - <value>(1017, 378)</value> + <key>tr_level</key> + <value>0.0</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>tr_mode</key> + <value>qtgui.TRIG_MODE_FREE</value> </param> - </block> - <block> - <key>qtgui_time_sink_x</key> <param> - <key>id</key> - <value>qtgui_time_sink_x_0_0</value> + <key>tr_slope</key> + <value>qtgui.TRIG_SLOPE_POS</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>tr_tag</key> + <value>""</value> </param> <param> <key>type</key> - <value>complex</value> + <value>float</value> </param> <param> - <key>name</key> - <value>Scope Plot</value> + <key>update_time</key> + <value>0.10</value> </param> <param> <key>ylabel</key> @@ -2222,473 +2506,398 @@ <value>""</value> </param> <param> - <key>size</key> - <value>1024</value> + <key>ymax</key> + <value>1</value> </param> <param> - <key>srate</key> - <value>samp_rate</value> + <key>ymin</key> + <value>-1</value> </param> + </block> + <block> + <key>qtgui_time_sink_x</key> <param> - <key>grid</key> - <value>False</value> + <key>autoscale</key> + <value>True</value> </param> <param> - <key>autoscale</key> - <value>False</value> + <key>axislabels</key> + <value>True</value> </param> <param> - <key>ymin</key> - <value>-1</value> + <key>alias</key> + <value></value> </param> <param> - <key>ymax</key> - <value>1</value> + <key>comment</key> + <value></value> </param> <param> - <key>nconnections</key> - <value>1</value> + <key>ctrlpanel</key> + <value>False</value> </param> <param> - <key>update_time</key> - <value>0.10</value> + <key>affinity</key> + <value></value> </param> <param> <key>entags</key> <value>True</value> </param> <param> + <key>_enabled</key> + <value>True</value> + </param> + <param> + <key>_coordinate</key> + <value>(1014, 622)</value> + </param> + <param> <key>gui_hint</key> - <value></value> + <value>notebook@1</value> </param> <param> - <key>tr_mode</key> - <value>qtgui.TRIG_MODE_FREE</value> + <key>_rotation</key> + <value>0</value> </param> <param> - <key>tr_slope</key> - <value>qtgui.TRIG_SLOPE_POS</value> + <key>grid</key> + <value>False</value> </param> <param> - <key>tr_level</key> - <value>0.0</value> + <key>id</key> + <value>qtgui_time_sink_x_1_1</value> </param> <param> - <key>tr_delay</key> - <value>0</value> + <key>legend</key> + <value>True</value> </param> <param> - <key>tr_chan</key> - <value>0</value> + <key>alpha1</key> + <value>1.0</value> </param> <param> - <key>tr_tag</key> - <value>""</value> + <key>color1</key> + <value>"blue"</value> </param> <param> <key>label1</key> <value></value> </param> <param> + <key>marker1</key> + <value>-1</value> + </param> + <param> + <key>style1</key> + <value>1</value> + </param> + <param> <key>width1</key> <value>1</value> </param> <param> - <key>color1</key> + <key>alpha10</key> + <value>1.0</value> + </param> + <param> + <key>color10</key> <value>"blue"</value> </param> <param> - <key>style1</key> - <value>1</value> + <key>label10</key> + <value></value> </param> <param> - <key>marker1</key> + <key>marker10</key> <value>-1</value> </param> <param> - <key>alpha1</key> - <value>1.0</value> + <key>style10</key> + <value>1</value> </param> <param> - <key>label2</key> - <value></value> + <key>width10</key> + <value>1</value> </param> <param> - <key>width2</key> - <value>1</value> + <key>alpha2</key> + <value>1.0</value> </param> <param> <key>color2</key> <value>"red"</value> </param> <param> - <key>style2</key> - <value>1</value> + <key>label2</key> + <value></value> </param> <param> <key>marker2</key> <value>-1</value> </param> <param> - <key>alpha2</key> - <value>1.0</value> + <key>style2</key> + <value>1</value> </param> <param> - <key>label3</key> - <value></value> + <key>width2</key> + <value>1</value> </param> <param> - <key>width3</key> - <value>1</value> + <key>alpha3</key> + <value>1.0</value> </param> <param> <key>color3</key> <value>"green"</value> </param> <param> - <key>style3</key> - <value>1</value> + <key>label3</key> + <value></value> </param> <param> <key>marker3</key> <value>-1</value> </param> <param> - <key>alpha3</key> - <value>1.0</value> + <key>style3</key> + <value>1</value> </param> <param> - <key>label4</key> - <value></value> + <key>width3</key> + <value>1</value> </param> <param> - <key>width4</key> - <value>1</value> + <key>alpha4</key> + <value>1.0</value> </param> <param> <key>color4</key> <value>"black"</value> </param> <param> - <key>style4</key> - <value>1</value> + <key>label4</key> + <value></value> </param> <param> <key>marker4</key> <value>-1</value> </param> <param> - <key>alpha4</key> - <value>1.0</value> + <key>style4</key> + <value>1</value> </param> <param> - <key>label5</key> - <value></value> + <key>width4</key> + <value>1</value> </param> <param> - <key>width5</key> - <value>1</value> + <key>alpha5</key> + <value>1.0</value> </param> <param> <key>color5</key> <value>"cyan"</value> </param> <param> - <key>style5</key> - <value>1</value> + <key>label5</key> + <value></value> </param> <param> <key>marker5</key> <value>-1</value> </param> <param> - <key>alpha5</key> - <value>1.0</value> + <key>style5</key> + <value>1</value> </param> <param> - <key>label6</key> - <value></value> + <key>width5</key> + <value>1</value> </param> <param> - <key>width6</key> - <value>1</value> + <key>alpha6</key> + <value>1.0</value> </param> <param> <key>color6</key> <value>"magenta"</value> </param> <param> - <key>style6</key> - <value>1</value> + <key>label6</key> + <value></value> </param> <param> <key>marker6</key> <value>-1</value> </param> <param> - <key>alpha6</key> - <value>1.0</value> + <key>style6</key> + <value>1</value> </param> <param> - <key>label7</key> - <value></value> + <key>width6</key> + <value>1</value> </param> <param> - <key>width7</key> - <value>1</value> + <key>alpha7</key> + <value>1.0</value> </param> <param> <key>color7</key> <value>"yellow"</value> </param> <param> - <key>style7</key> - <value>1</value> + <key>label7</key> + <value></value> </param> <param> <key>marker7</key> <value>-1</value> </param> <param> - <key>alpha7</key> - <value>1.0</value> + <key>style7</key> + <value>1</value> </param> <param> - <key>label8</key> - <value></value> + <key>width7</key> + <value>1</value> </param> <param> - <key>width8</key> - <value>1</value> + <key>alpha8</key> + <value>1.0</value> </param> <param> <key>color8</key> <value>"dark red"</value> </param> <param> - <key>style8</key> - <value>1</value> + <key>label8</key> + <value></value> </param> <param> <key>marker8</key> <value>-1</value> </param> <param> - <key>alpha8</key> - <value>1.0</value> + <key>style8</key> + <value>1</value> </param> <param> - <key>label9</key> - <value></value> + <key>width8</key> + <value>1</value> </param> <param> - <key>width9</key> - <value>1</value> + <key>alpha9</key> + <value>1.0</value> </param> <param> <key>color9</key> <value>"dark green"</value> </param> <param> - <key>style9</key> - <value>1</value> + <key>label9</key> + <value></value> </param> <param> <key>marker9</key> <value>-1</value> </param> <param> - <key>alpha9</key> - <value>1.0</value> - </param> - <param> - <key>label10</key> - <value></value> - </param> - <param> - <key>width10</key> + <key>style9</key> <value>1</value> </param> <param> - <key>color10</key> - <value>"blue"</value> - </param> - <param> - <key>style10</key> + <key>width9</key> <value>1</value> </param> <param> - <key>marker10</key> - <value>-1</value> - </param> - <param> - <key>alpha10</key> - <value>1.0</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(1029, 281)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> + <key>name</key> + <value>Phase</value> </param> - </block> - <block> - <key>variable_qtgui_range</key> <param> - <key>id</key> - <value>time_bw</value> + <key>nconnections</key> + <value>1</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>size</key> + <value>1024</value> </param> <param> - <key>label</key> - <value>Timing Loop BW</value> + <key>srate</key> + <value>samp_rate</value> </param> <param> - <key>value</key> + <key>tr_chan</key> <value>0</value> </param> <param> - <key>start</key> + <key>tr_delay</key> <value>0</value> </param> <param> - <key>stop</key> - <value>.1</value> - </param> - <param> - <key>step</key> - <value>.001</value> - </param> - <param> - <key>widget</key> - <value>counter_slider</value> - </param> - <param> - <key>orient</key> - <value>Qt.Horizontal</value> - </param> - <param> - <key>min_len</key> - <value>200</value> - </param> - <param> - <key>gui_hint</key> - <value>1,2,1,1</value> - </param> - <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>_coordinate</key> - <value>(451, 0)</value> + <key>tr_level</key> + <value>0.0</value> </param> <param> - <key>_rotation</key> - <value>0</value> + <key>tr_mode</key> + <value>qtgui.TRIG_MODE_FREE</value> </param> - </block> - <block> - <key>digital_pfb_clock_sync_xxx</key> <param> - <key>id</key> - <value>digital_pfb_clock_sync_xxx_0</value> + <key>tr_slope</key> + <value>qtgui.TRIG_SLOPE_POS</value> </param> <param> - <key>_enabled</key> - <value>True</value> + <key>tr_tag</key> + <value>""</value> </param> <param> <key>type</key> - <value>ccf</value> - </param> - <param> - <key>sps</key> - <value>spb</value> - </param> - <param> - <key>loop_bw</key> - <value>time_bw</value> - </param> - <param> - <key>taps</key> - <value>firdes.root_raised_cosine(nfilts, nfilts*spb, 1.0, rolloff, 44*nfilts)</value> + <value>float</value> </param> <param> - <key>filter_size</key> - <value>nfilts</value> + <key>update_time</key> + <value>0.10</value> </param> <param> - <key>init_phase</key> - <value>16</value> + <key>ylabel</key> + <value>Amplitude</value> </param> <param> - <key>max_dev</key> - <value>1.5</value> + <key>yunit</key> + <value>""</value> </param> <param> - <key>osps</key> + <key>ymax</key> <value>1</value> </param> <param> - <key>alias</key> - <value></value> - </param> - <param> - <key>affinity</key> - <value></value> - </param> - <param> - <key>minoutbuf</key> - <value>0</value> - </param> - <param> - <key>maxoutbuf</key> - <value>0</value> - </param> - <param> - <key>_coordinate</key> - <value>(467, 403)</value> - </param> - <param> - <key>_rotation</key> - <value>0</value> + <key>ymin</key> + <value>-1</value> </param> </block> <connection> - <source_block_id>blocks_throttle_0</source_block_id> - <sink_block_id>digital_pfb_clock_sync_xxx_0</sink_block_id> + <source_block_id>analog_random_source_x</source_block_id> + <sink_block_id>digital_chunks_to_symbols_xx</sink_block_id> <source_key>0</source_key> <sink_key>0</sink_key> </connection> <connection> - <source_block_id>pfb_arb_resampler_xxx_0</source_block_id> - <sink_block_id>blocks_multiply_const_vxx_0</sink_block_id> + <source_block_id>blocks_multiply_const_vxx_0</source_block_id> + <sink_block_id>channels_channel_model_0</sink_block_id> <source_key>0</source_key> <sink_key>0</sink_key> </connection> <connection> - <source_block_id>digital_chunks_to_symbols_xx</source_block_id> - <sink_block_id>pfb_arb_resampler_xxx_0</sink_block_id> + <source_block_id>blocks_multiply_const_vxx_0</source_block_id> + <sink_block_id>qtgui_time_sink_x_0</sink_block_id> <source_key>0</source_key> <sink_key>0</sink_key> </connection> <connection> - <source_block_id>analog_random_source_x</source_block_id> - <sink_block_id>digital_chunks_to_symbols_xx</sink_block_id> + <source_block_id>blocks_throttle_0</source_block_id> + <sink_block_id>digital_pfb_clock_sync_xxx_0</sink_block_id> <source_key>0</source_key> <sink_key>0</sink_key> </connection> @@ -2699,15 +2908,15 @@ <sink_key>0</sink_key> </connection> <connection> - <source_block_id>blocks_multiply_const_vxx_0</source_block_id> - <sink_block_id>channels_channel_model_0</sink_block_id> + <source_block_id>digital_chunks_to_symbols_xx</source_block_id> + <sink_block_id>pfb_arb_resampler_xxx_0</sink_block_id> <source_key>0</source_key> <sink_key>0</sink_key> </connection> <connection> - <source_block_id>blocks_multiply_const_vxx_0</source_block_id> - <sink_block_id>qtgui_time_sink_x_0</sink_block_id> - <source_key>0</source_key> + <source_block_id>digital_pfb_clock_sync_xxx_0</source_block_id> + <sink_block_id>qtgui_time_sink_x_1</sink_block_id> + <source_key>1</source_key> <sink_key>0</sink_key> </connection> <connection> @@ -2718,12 +2927,6 @@ </connection> <connection> <source_block_id>digital_pfb_clock_sync_xxx_0</source_block_id> - <sink_block_id>qtgui_time_sink_x_1</sink_block_id> - <source_key>1</source_key> - <sink_key>0</sink_key> - </connection> - <connection> - <source_block_id>digital_pfb_clock_sync_xxx_0</source_block_id> <sink_block_id>qtgui_time_sink_x_1_1</sink_block_id> <source_key>3</source_key> <sink_key>0</sink_key> @@ -2734,4 +2937,10 @@ <source_key>2</source_key> <sink_key>0</sink_key> </connection> + <connection> + <source_block_id>pfb_arb_resampler_xxx_0</source_block_id> + <sink_block_id>blocks_multiply_const_vxx_0</sink_block_id> + <source_key>0</source_key> + <sink_key>0</sink_key> + </connection> </flow_graph> diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt index 5a32c7cd70..d0002325b6 100644 --- a/grc/CMakeLists.txt +++ b/grc/CMakeLists.txt @@ -33,12 +33,6 @@ GR_PYTHON_CHECK_MODULE_RAW( ) GR_PYTHON_CHECK_MODULE_RAW( - "Cheetah >= 2.0.0" - "import Cheetah; assert Cheetah.Version >= '2.0.0'" - CHEETAH_FOUND -) - -GR_PYTHON_CHECK_MODULE_RAW( "PyYAML >= 3.10" "import yaml; assert yaml.__version__ >= '3.11'" PYYAML_FOUND @@ -94,7 +88,8 @@ include(GrComponent) if(NOT CMAKE_CROSSCOMPILING) set(grc_python_deps PYTHON_MIN_VER_FOUND - CHEETAH_FOUND + PYYAML_FOUND + MAKO_FOUND LXML_FOUND PYGI_FOUND GTK_GI_FOUND diff --git a/grc/blocks/block_tree.xml b/grc/blocks/block_tree.xml index 3125864d4d..2a063f1f60 100644 --- a/grc/blocks/block_tree.xml +++ b/grc/blocks/block_tree.xml @@ -8,12 +8,6 @@ <block>virtual_source</block> <block>virtual_sink</block> - <block>bus_sink</block> - <block>bus_source</block> - <block>bus_structure_sink</block> - <block>bus_structure_source</block> - - <block>epy_block</block> <block>epy_module</block> <block>note</block> diff --git a/grc/blocks/bus_sink.xml b/grc/blocks/bus_sink.xml deleted file mode 100644 index 029820dc2c..0000000000 --- a/grc/blocks/bus_sink.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Bus Sink -################################################### - --> -<block> - <name>Bus Sink</name> - <key>bus_sink</key> - <make>$yesno.yesno</make> - - <param> - <name>On/Off</name> - <key>yesno</key> - <type>enum</type> - <option> - <name>On</name> - <key>on</key> - <opt>yesno:True</opt> - </option> - <option> - <name>Off</name> - <key>off</key> - <opt>yesno:False</opt> - </option> - </param> -</block> diff --git a/grc/blocks/bus_source.xml b/grc/blocks/bus_source.xml deleted file mode 100644 index e5b5c2b9bf..0000000000 --- a/grc/blocks/bus_source.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Bus Sink -################################################### - --> -<block> - <name>Bus Source</name> - <key>bus_source</key> - <make>$yesno.yesno</make> - - <param> - <name>On/Off</name> - <key>yesno</key> - <type>enum</type> - <option> - <name>On</name> - <key>on</key> - <opt>yesno:True</opt> - </option> - <option> - <name>Off</name> - <key>off</key> - <opt>yesno:False</opt> - </option> - </param> -</block> diff --git a/grc/blocks/bus_structure_sink.xml b/grc/blocks/bus_structure_sink.xml deleted file mode 100644 index 3adac92810..0000000000 --- a/grc/blocks/bus_structure_sink.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Bus Sink -################################################### - --> -<block> - <name>Bus Sink Structure</name> - <key>bus_structure_sink</key> - <make>None</make> - - <param> - <name>Structure</name> - <key>struct</key> - <value></value> - <type>raw</type> - </param> -</block> diff --git a/grc/blocks/bus_structure_source.xml b/grc/blocks/bus_structure_source.xml deleted file mode 100644 index 34e7c049a2..0000000000 --- a/grc/blocks/bus_structure_source.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##Bus Sink -################################################### - --> -<block> - <name>Bus Source Structure</name> - <key>bus_structure_source</key> - <make>None</make> - - <param> - <name>Structure</name> - <key>struct</key> - <value></value> - <type>raw</type> - </param> -</block> diff --git a/grc/blocks/epy_block.xml b/grc/blocks/epy_block.xml deleted file mode 100644 index 65e78c4062..0000000000 --- a/grc/blocks/epy_block.xml +++ /dev/null @@ -1,58 +0,0 @@ -<?xml version="1.0"?> -<block> - <name>Python Block</name> - <key>epy_block</key> - <import></import> - <make></make> - <param><!-- Cache the last working block IO to keep FG sane --> - <name>Block Io</name> - <key>_io_cache</key> - <type>string</type> - <hide>all</hide> - </param> - <param> - <name>Code</name> - <key>_source_code</key> - <value>""" -Embedded Python Blocks: - -Each time this file is saved, GRC will instantiate the first class it finds -to get ports and parameters of your block. The arguments to __init__ will -be the parameters. All of them are required to have default values! -""" - -import numpy as np -from gnuradio import gr - - -class blk(gr.sync_block): # other base classes are basic_block, decim_block, interp_block - """Embedded Python Block example - a simple multiply const""" - - def __init__(self, example_param=1.0): # only default arguments here - """arguments to this function show up as parameters in GRC""" - gr.sync_block.__init__( - self, - name='Embedded Python Block', # will show up in GRC - in_sig=[np.complex64], - out_sig=[np.complex64] - ) - # if an attribute with the same name as a parameter is found, - # a callback is registered (properties work, too). - self.example_param = example_param - - def work(self, input_items, output_items): - """example: multiply with constant""" - output_items[0][:] = input_items[0] * self.example_param - return len(output_items[0]) -</value> - <type>_multiline_python_external</type> - <hide>part</hide> - </param> - <doc>This block represents an arbitrary GNU Radio Python Block. - -Its source code can be accessed through the parameter 'Code' which opens your editor. Each time you save changes in the editor, GRC will update the block. This includes the number, names and defaults of the parameters, the ports (stream and message) and the block name and documentation. - -Block Documentation: -(will be replaced the docstring of your block class) -</doc> -</block> diff --git a/grc/blocks/epy_module.xml b/grc/blocks/epy_module.xml deleted file mode 100644 index fa3e5f91f4..0000000000 --- a/grc/blocks/epy_module.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0"?> -<block> - <name>Python Module</name> - <key>epy_module</key> - <import>import $id # embedded python module</import> - <make></make> - <param> - <name>Code</name> - <key>source_code</key> - <value># this module will be imported in the into your flowgraph</value> - <type>_multiline_python_external</type> - <hide>part</hide> - </param> - <doc>This block lets you embed a python module in your flowgraph. - -Code you put in this module is accessible in other blocks using the ID of this -block. Example: - -If you put - - a = 2 - - def double(arg): - return 2 * arg - -in a Python Module Block with the ID 'stuff' you can use code like - - stuff.a # evals to 2 - stuff.double(3) # evals to 6 - -to set parameters of other blocks in your flowgraph.</doc> -</block> diff --git a/grc/blocks/gr_message_domain.xml b/grc/blocks/gr_message_domain.xml deleted file mode 100644 index bc8add99ab..0000000000 --- a/grc/blocks/gr_message_domain.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##GNU Radio default domain 'gr_message' -################################################### - --> - <domain> - <name>GR Message</name> - <key>gr_message</key> - <color>#000</color> - <multiple_sources>True</multiple_sources> - <connection> - <source_domain>gr_message</source_domain> - <sink_domain>gr_message</sink_domain> - <make>#slurp - self.msg_connect($make_port_sig($source), $make_port_sig($sink))#slurp - </make> - </connection> -</domain> diff --git a/grc/blocks/gr_stream_domain.xml b/grc/blocks/gr_stream_domain.xml deleted file mode 100644 index 66026448ca..0000000000 --- a/grc/blocks/gr_stream_domain.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0"?> -<!-- -################################################### -##GNU Radio default domain 'gr_stream' -################################################### - --> - <domain> - <name>GR Stream</name> - <key>gr_stream</key> - <color>#000</color> - <connection> - <source_domain>gr_stream</source_domain> - <sink_domain>gr_stream</sink_domain> - <make>#slurp - self.connect($make_port_sig($source), $make_port_sig($sink))#slurp - </make> - </connection> -</domain> diff --git a/grc/blocks/import.xml b/grc/blocks/import.xml index 59f807bacb..58e99a2d01 100644 --- a/grc/blocks/import.xml +++ b/grc/blocks/import.xml @@ -7,11 +7,11 @@ <block> <name>Import</name> <key>import</key> - <import>$import</import> + <import>$imports</import> <make></make> <param> <name>Import</name> - <key>import</key> + <key>imports</key> <value></value> <type>import</type> </param> diff --git a/grc/blocks/message.domain.yml b/grc/blocks/message.domain.yml new file mode 100644 index 0000000000..7e6cc529d9 --- /dev/null +++ b/grc/blocks/message.domain.yml @@ -0,0 +1,10 @@ +id: message +label: Message +color: "#FFFFFF" + +multiple_connections_per_input: true +multiple_connections_per_output: true + +templates: +- type: [message, message] + connect: self.msg_connect(${ make_port_sig(source) }, ${ make_port_sig(sink) }) diff --git a/grc/blocks/options.xml b/grc/blocks/options.xml index 252a0b2e2d..c349cd956a 100644 --- a/grc/blocks/options.xml +++ b/grc/blocks/options.xml @@ -100,7 +100,7 @@ else: self.stop(); self.wait()</callback> <key>run</key> <value>True</value> <type>bool</type> - <hide>#if $generate_options() == 'qt_gui' then ('part' if $run() else 'none') else 'all'#</hide> + <hide>#if $generate_options() != 'qt_gui' then 'all' else ('part' if $run() else 'none')#</hide> <option> <name>Autostart</name> <key>True</key> @@ -137,7 +137,7 @@ else: self.stop(); self.wait()</callback> <key>qt_qss_theme</key> <value></value> <type>file_open</type> - <hide>#if $generate_options() == 'qt_gui' then ('none' if $qt_qss_theme() else 'part') else 'all'#</hide> + <hide>#if $generate_options() != 'qt_gui' then 'all' else ('none' if $qt_qss_theme() else 'part')#</hide> </param> <param> <name>Thread-safe setters</name> diff --git a/grc/blocks/stream.domain.yml b/grc/blocks/stream.domain.yml new file mode 100644 index 0000000000..a4d786f8b4 --- /dev/null +++ b/grc/blocks/stream.domain.yml @@ -0,0 +1,10 @@ +id: stream +label: Stream +color: "#000000" + +multiple_connections_per_input: false +multiple_connections_per_output: true + +templates: +- type: [stream, stream] + connect: self.connect(${ make_port_sig(source) }, ${ make_port_sig(sink) }) diff --git a/grc/compiler.py b/grc/compiler.py index b2361b86eb..a5f6c3edcd 100755 --- a/grc/compiler.py +++ b/grc/compiler.py @@ -26,7 +26,7 @@ import subprocess from gnuradio import gr from .core import Messages -from .core.Platform import Platform +from .core.platform import Platform def argument_parser(): @@ -53,6 +53,8 @@ def main(args=None): version=gr.version(), version_parts=(gr.major_version(), gr.api_version(), gr.minor_version()) ) + platform.build_library() + out_dir = args.output if not args.user_lib_dir else platform.config.hier_block_lib_dir if os.path.exists(out_dir): pass # all is well diff --git a/grc/converter/__init__.py b/grc/converter/__init__.py new file mode 100644 index 0000000000..224f2e9afc --- /dev/null +++ b/grc/converter/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2016 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 __future__ import absolute_import + +from .main import Converter diff --git a/grc/converter/__main__.py b/grc/converter/__main__.py new file mode 100644 index 0000000000..6efc2d7c59 --- /dev/null +++ b/grc/converter/__main__.py @@ -0,0 +1,21 @@ +# Copyright 2016 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 __future__ import absolute_import + +# TODO: implement cli + diff --git a/grc/core/block.dtd b/grc/converter/block.dtd index 145f4d8610..145f4d8610 100644 --- a/grc/core/block.dtd +++ b/grc/converter/block.dtd diff --git a/grc/converter/block.py b/grc/converter/block.py new file mode 100644 index 0000000000..04e5c905a0 --- /dev/null +++ b/grc/converter/block.py @@ -0,0 +1,219 @@ +# Copyright 2016 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 +""" +Converter for legacy block definitions in XML format + +- Cheetah expressions that can not be converted are passed to Cheetah for now +- Instead of generating a Block subclass directly a string representation is + used and evaluated. This is slower / lamer but allows us to show the user + how a converted definition would look like +""" + +from __future__ import absolute_import, division, print_function + +from collections import OrderedDict, defaultdict +from itertools import chain + +from ..core.io import yaml +from . import cheetah_converter, xml + +current_file_format = 1 +reserved_block_keys = ('import', ) # todo: add more keys + + +def from_xml(filename): + """Load block description from xml file""" + element, version_info = xml.load(filename, 'block.dtd') + + try: + data = convert_block_xml(element) + except NameError: + raise ValueError('Conversion failed', filename) + + return data + + +def dump(data, stream): + out = yaml.dump(data) + + replace = [ + ('parameters:', '\nparameters:'), + ('inputs:', '\ninputs:'), + ('outputs:', '\noutputs:'), + ('templates:', '\ntemplates:'), + ('documentation:', '\ndocumentation:'), + ('file_format:', '\nfile_format:'), + ] + for r in replace: + out = out.replace(*r) + prefix = '# auto-generated by grc.converter\n\n' + stream.write(prefix + out) + + +no_value = object() +dummy = cheetah_converter.DummyConverter() + + +def convert_block_xml(node): + converter = cheetah_converter.Converter(names={ + param_node.findtext('key'): { + opt_node.text.split(':')[0] + for opt_node in next(param_node.iterfind('option'), param_node).iterfind('opt') + } for param_node in node.iterfind('param') + }) + + block_id = node.findtext('key') + if block_id in reserved_block_keys: + block_id += '_' + + data = OrderedDict() + data['id'] = block_id + data['label'] = node.findtext('name') or no_value + data['category'] = node.findtext('category') or no_value + data['flags'] = node.findtext('flags') or no_value + + data['parameters'] = [convert_param_xml(param_node, converter.to_python_dec) + for param_node in node.iterfind('param')] or no_value + # data['params'] = {p.pop('key'): p for p in data['params']} + + data['inputs'] = [convert_port_xml(port_node, converter.to_python_dec) + for port_node in node.iterfind('sink')] or no_value + + data['outputs'] = [convert_port_xml(port_node, converter.to_python_dec) + for port_node in node.iterfind('source')] or no_value + + data['checks'] = [converter.to_python_dec(check_node.text) + for check_node in node.iterfind('checks')] or no_value + data['value'] = ( + converter.to_python_dec(node.findtext('var_value')) or + ('${ value }' if block_id.startswith('variable') else no_value) + ) + + data['templates'] = convert_templates(node, converter.to_mako, block_id) or no_value + + docs = node.findtext('doc') + if docs: + docs = docs.strip().replace('\\\n', '') + data['documentation'] = yaml.MultiLineString(docs) + + data['file_format'] = current_file_format + + data = OrderedDict((key, value) for key, value in data.items() if value is not no_value) + auto_hide_params_for_item_sizes(data) + + return data + + +def auto_hide_params_for_item_sizes(data): + item_size_templates = [] + vlen_templates = [] + for port in chain(*[data.get(direction, []) for direction in ['inputs', 'outputs']]): + for key in ['dtype', 'multiplicity']: + item_size_templates.append(str(port.get(key, ''))) + vlen_templates.append(str(port.get('vlen', ''))) + item_size_templates = ' '.join(value for value in item_size_templates if '${' in value) + vlen_templates = ' '.join(value for value in vlen_templates if '${' in value) + + for param in data.get('parameters', []): + if param['id'] in item_size_templates: + param.setdefault('hide', 'part') + if param['id'] in vlen_templates: + param.setdefault('hide', "${ 'part' if vlen == 1 else 'none' }") + + +def convert_templates(node, convert, block_id=''): + templates = OrderedDict() + + imports = '\n'.join(convert(import_node.text) + for import_node in node.iterfind('import')) + if '\n' in imports: + imports = yaml.MultiLineString(imports) + templates['imports'] = imports or no_value + + templates['var_make'] = convert(node.findtext('var_make') or '') or no_value + + make = convert(node.findtext('make') or '') + if make: + check_mako_template(block_id, make) + if '\n' in make: + make = yaml.MultiLineString(make) + templates['make'] = make or no_value + + templates['callbacks'] = [ + convert(cb_node.text) for cb_node in node.iterfind('callback') + ] or no_value + + return OrderedDict((key, value) for key, value in templates.items() if value is not no_value) + + +def convert_param_xml(node, convert): + param = OrderedDict() + param['id'] = node.findtext('key').strip() + param['label'] = node.findtext('name').strip() + param['category'] = node.findtext('tab') or no_value + + param['dtype'] = convert(node.findtext('type') or '') + param['default'] = node.findtext('value') or no_value + + options = yaml.ListFlowing(on.findtext('key') for on in node.iterfind('option')) + option_labels = yaml.ListFlowing(on.findtext('name') for on in node.iterfind('option')) + param['options'] = options or no_value + if not all(str(o).title() == l for o, l in zip(options, option_labels)): + param['option_labels'] = option_labels + + attributes = defaultdict(yaml.ListFlowing) + for option_n in node.iterfind('option'): + for opt_n in option_n.iterfind('opt'): + key, value = opt_n.text.split(':', 2) + attributes[key].append(value) + param['option_attributes'] = dict(attributes) or no_value + + param['hide'] = convert(node.findtext('hide')) or no_value + + return OrderedDict((key, value) for key, value in param.items() if value is not no_value) + + +def convert_port_xml(node, convert): + port = OrderedDict() + label = node.findtext('name') + # default values: + port['label'] = label if label not in ('in', 'out') else no_value + + dtype = convert(node.findtext('type')) + # TODO: detect dyn message ports + port['domain'] = domain = 'message' if dtype == 'message' else 'stream' + if domain == 'message': + port['id'], port['label'] = label, no_value + else: + port['dtype'] = dtype + vlen = node.findtext('vlen') + port['vlen'] = int(vlen) if vlen and vlen.isdigit() else convert(vlen) or no_value + + port['multiplicity'] = convert(node.findtext('nports')) or no_value + port['optional'] = bool(node.findtext('optional')) or no_value + port['hide'] = convert(node.findtext('hide')) or no_value + + return OrderedDict((key, value) for key, value in port.items() if value is not no_value) + + +def check_mako_template(block_id, expr): + import sys + from mako.template import Template + try: + Template(expr) + except Exception as error: + print(block_id, expr, type(error), error, '', sep='\n', file=sys.stderr) diff --git a/grc/core/block_tree.dtd b/grc/converter/block_tree.dtd index 9e23576477..9e23576477 100644 --- a/grc/core/block_tree.dtd +++ b/grc/converter/block_tree.dtd diff --git a/grc/converter/block_tree.py b/grc/converter/block_tree.py new file mode 100644 index 0000000000..dee9adba49 --- /dev/null +++ b/grc/converter/block_tree.py @@ -0,0 +1,56 @@ +# Copyright 2016 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 +""" +Converter for legacy block tree definitions in XML format +""" + +from __future__ import absolute_import, print_function + +from ..core.io import yaml +from . import xml + + +def from_xml(filename): + """Load block tree description from xml file""" + element, version_info = xml.load(filename, 'block_tree.dtd') + + try: + data = convert_category_node(element) + except NameError: + raise ValueError('Conversion failed', filename) + + return data + + +def dump(data, stream): + out = yaml.dump(data, indent=2) + prefix = '# auto-generated by grc.converter\n\n' + stream.write(prefix + out) + + +def convert_category_node(node): + """convert nested <cat> tags to nested lists dicts""" + assert node.tag == 'cat' + name, elements = '', [] + for child in node: + if child.tag == 'name': + name = child.text.strip() + elif child.tag == 'block': + elements.append(child.text.strip()) + elif child.tag == 'cat': + elements.append(convert_category_node(child)) + return {name: elements} diff --git a/grc/converter/cheetah_converter.py b/grc/converter/cheetah_converter.py new file mode 100644 index 0000000000..16fea32c99 --- /dev/null +++ b/grc/converter/cheetah_converter.py @@ -0,0 +1,277 @@ +# Copyright 2016 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 __future__ import absolute_import, print_function + +import collections +import re +import string + +delims = {'(': ')', '[': ']', '{': '}', '': ', #\\*:'} +identifier_start = '_' + string.ascii_letters + ''.join(delims.keys()) +string_delims = '"\'' + +cheetah_substitution = re.compile( + r'^\$((?P<d1>\()|(?P<d2>\{)|(?P<d3>\[)|)' + r'(?P<arg>[_a-zA-Z][_a-zA-Z0-9]*(?:\.[_a-zA-Z][_a-zA-Z0-9]*)?)(?P<eval>\(\))?' + r'(?(d1)\)|(?(d2)\}|(?(d3)\]|)))$' +) +cheetah_inline_if = re.compile(r'#if (?P<cond>.*) then (?P<then>.*?) ?else (?P<else>.*?) ?(#|$)') + + +class Python(object): + start = '' + end = '' + nested_start = '' + nested_end = '' + eval = '' + type = str # yaml_output.Eval + + +class FormatString(Python): + start = '{' + end = '}' + nested_start = '{' + nested_end = '}' + eval = ':eval' + type = str + + +class Mako(Python): + start = '${' + end = '}' + nested_start = '' + nested_end = '' + type = str + + +class Converter(object): + + def __init__(self, names): + self.stats = collections.defaultdict(int) + self.names = set(names) + self.extended = set(self._iter_identifiers(names)) + + @staticmethod + def _iter_identifiers(names): + if not isinstance(names, dict): + names = {name: {} for name in names} + for key, sub_keys in names.items(): + yield key + for sub_key in sub_keys: + yield '{}.{}'.format(key, sub_key) + + def to_python(self, expr): + return self.convert(expr=expr, spec=Python) + + def to_python_dec(self, expr): + converted = self.convert(expr=expr, spec=Python) + if converted and converted != expr: + converted = '${ ' + converted.strip() + ' }' + return converted + + def to_format_string(self, expr): + return self.convert(expr=expr, spec=FormatString) + + def to_mako(self, expr): + return self.convert(expr=expr, spec=Mako) + + def convert(self, expr, spec=Python): + if not expr: + return '' + + elif '$' not in expr: + return expr + + try: + return self.convert_simple(expr, spec) + except ValueError: + pass + + try: + if '#if' in expr and '\n' not in expr: + expr = self.convert_inline_conditional(expr, spec) + return self.convert_hard(expr, spec) + except ValueError: + return 'Cheetah! ' + expr + + def convert_simple(self, expr, spec=Python): + match = cheetah_substitution.match(expr) + if not match: + raise ValueError('Not a simple substitution: ' + expr) + + identifier = match.group('arg') + if identifier not in self.extended: + raise NameError('Unknown substitution {!r}'.format(identifier)) + if match.group('eval'): + identifier += spec.eval + + out = spec.start + identifier + spec.end + if '$' in out or '#' in out: + raise ValueError('Failed to convert: ' + expr) + + self.stats['simple'] += 1 + return spec.type(out) + + def convert_hard(self, expr, spec=Python): + lines = '\n'.join(self.convert_hard_line(line, spec) for line in expr.split('\n')) + if spec == Mako: + # no line-continuation before a mako control structure + lines = re.sub(r'\\\n(\s*%)', r'\n\1', lines) + return lines + + def convert_hard_line(self, expr, spec=Python): + if spec == Mako: + if '#set' in expr: + ws, set_, statement = expr.partition('#set ') + return ws + '<% ' + self.to_python(statement) + ' %>' + + if '#if' in expr: + ws, if_, condition = expr.partition('#if ') + return ws + '% if ' + self.to_python(condition) + ':' + if '#else if' in expr: + ws, elif_, condition = expr.partition('#else if ') + return ws + '% elif ' + self.to_python(condition) + ':' + if '#else' in expr: + return expr.replace('#else', '% else:') + if '#end if' in expr: + return expr.replace('#end if', '% endif') + + if '#slurp' in expr: + expr = expr.split('#slurp', 1)[0] + '\\' + return self.convert_hard_replace(expr, spec) + + def convert_hard_replace(self, expr, spec=Python): + counts = collections.Counter() + + def all_delims_closed(): + for opener_, closer_ in delims.items(): + if counts[opener_] != counts[closer_]: + return False + return True + + def extra_close(): + for opener_, closer_ in delims.items(): + if counts[opener_] < counts[closer_]: + return True + return False + + out = [] + delim_to_find = False + + pos = 0 + char = '' + in_string = None + while pos < len(expr): + prev, char = char, expr[pos] + counts.update(char) + + if char in string_delims: + if not in_string: + in_string = char + elif char == in_string: + in_string = None + out.append(char) + pos += 1 + continue + if in_string: + out.append(char) + pos += 1 + continue + + if char == '$': + pass # no output + + elif prev == '$': + if char not in identifier_start: # not a substitution + out.append('$' + char) # now print the $ we skipped over + + elif not delim_to_find: # start of a substitution + try: + delim_to_find = delims[char] + out.append(spec.start) + except KeyError: + if char in identifier_start: + delim_to_find = delims[''] + out.append(spec.start) + out.append(char) + + counts.clear() + counts.update(char) + + else: # nested substitution: simply match known variable names + found = False + for known_identifier in self.names: + if expr[pos:].startswith(known_identifier): + found = True + break + if found: + out.append(spec.nested_start) + out.append(known_identifier) + out.append(spec.nested_end) + pos += len(known_identifier) + continue + + elif delim_to_find and char in delim_to_find and all_delims_closed(): # end of substitution + out.append(spec.end) + if char in delims['']: + out.append(char) + delim_to_find = False + + elif delim_to_find and char in ')]}' and extra_close(): # end of substitution + out.append(spec.end) + out.append(char) + delim_to_find = False + + else: + out.append(char) + + pos += 1 + + if delim_to_find == delims['']: + out.append(spec.end) + + out = ''.join(out) + # fix: eval stuff + out = re.sub(r'(?P<arg>' + r'|'.join(self.extended) + r')\(\)', '\g<arg>', out) + + self.stats['hard'] += 1 + return spec.type(out) + + def convert_inline_conditional(self, expr, spec=Python): + if spec == FormatString: + raise ValueError('No conditionals in format strings: ' + expr) + matcher = r'\g<then> if \g<cond> else \g<else>' + if spec == Python: + matcher = '(' + matcher + ')' + expr = cheetah_inline_if.sub(matcher, expr) + return spec.type(self.convert_hard(expr, spec)) + + +class DummyConverter(object): + + def __init__(self, names={}): + pass + + def to_python(self, expr): + return expr + + def to_format_string(self, expr): + return expr + + def to_mako(self, expr): + return expr diff --git a/grc/converter/flow_graph.dtd b/grc/converter/flow_graph.dtd new file mode 100644 index 0000000000..bdfe1dc059 --- /dev/null +++ b/grc/converter/flow_graph.dtd @@ -0,0 +1,38 @@ +<!-- +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 +--> +<!-- + flow_graph.dtd + Josh Blum + The document type definition for flow graph xml files. + --> +<!ELEMENT flow_graph (timestamp?, block*, connection*)> <!-- optional timestamp --> +<!ELEMENT timestamp (#PCDATA)> +<!-- Block --> +<!ELEMENT block (key, param*, bus_sink?, bus_source?)> +<!ELEMENT param (key, value)> +<!ELEMENT key (#PCDATA)> +<!ELEMENT value (#PCDATA)> +<!ELEMENT bus_sink (#PCDATA)> +<!ELEMENT bus_source (#PCDATA)> +<!-- Connection --> +<!ELEMENT connection (source_block_id, sink_block_id, source_key, sink_key)> +<!ELEMENT source_block_id (#PCDATA)> +<!ELEMENT sink_block_id (#PCDATA)> +<!ELEMENT source_key (#PCDATA)> +<!ELEMENT sink_key (#PCDATA)> diff --git a/grc/converter/flow_graph.py b/grc/converter/flow_graph.py new file mode 100644 index 0000000000..d20c67703c --- /dev/null +++ b/grc/converter/flow_graph.py @@ -0,0 +1,131 @@ +# Copyright 2017 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 __future__ import absolute_import, division + +import ast +from collections import OrderedDict + +from ..core.io import yaml +from . import xml + + +def from_xml(filename): + """Load flow graph from xml file""" + element, version_info = xml.load(filename, 'flow_graph.dtd') + + data = convert_flow_graph_xml(element) + try: + file_format = int(version_info['format']) + except KeyError: + file_format = _guess_file_format_1(data) + + data['metadata'] = {'file_format': file_format} + + return data + + +def dump(data, stream): + out = yaml.dump(data, indent=2) + + replace = [ + ('blocks:', '\nblocks:'), + ('connections:', '\nconnections:'), + ('metadata:', '\nmetadata:'), + ] + for r in replace: + out = out.replace(*r) + prefix = '# auto-generated by grc.converter\n\n' + stream.write(prefix + out) + + +def convert_flow_graph_xml(node): + blocks = [ + convert_block(block_data) + for block_data in node.findall('block') + ] + + options = next(b for b in blocks if b['id'] == 'options') + blocks.remove(options) + options.pop('id') + + connections = [ + convert_connection(connection) + for connection in node.findall('connection') + ] + + flow_graph = OrderedDict() + flow_graph['options'] = options + flow_graph['blocks'] = blocks + flow_graph['connections'] = connections + return flow_graph + + +def convert_block(data): + block_id = data.findtext('key') + + params = OrderedDict(sorted( + (param.findtext('key'), param.findtext('value')) + for param in data.findall('param') + )) + states = OrderedDict() + x, y = ast.literal_eval(params.pop('_coordinate', '(10, 10)')) + states['coordinate'] = yaml.ListFlowing([x, y]) + states['rotation'] = int(params.pop('_rotation', '0')) + enabled = params.pop('_enabled', 'True') + states['state'] = ( + 'enabled' if enabled in ('1', 'True') else + 'bypassed' if enabled == '2' else + 'disabled' + ) + + block = OrderedDict() + if block_id != 'options': + block['name'] = params.pop('id') + block['id'] = block_id + block['parameters'] = params + block['states'] = states + + return block + + +def convert_connection(data): + src_blk_id = data.findtext('source_block_id') + src_port_id = data.findtext('source_key') + snk_blk_id = data.findtext('sink_block_id') + snk_port_id = data.findtext('sink_key') + + if src_port_id.isdigit(): + src_port_id = 'out' + src_port_id + if snk_port_id.isdigit(): + snk_port_id = 'in' + snk_port_id + + return yaml.ListFlowing([src_blk_id, src_port_id, snk_blk_id, snk_port_id]) + + +def _guess_file_format_1(data): + """Try to guess the file format for flow-graph files without version tag""" + + def has_numeric_port_ids(src_id, src_port_id, snk_id, snk_port_id): + return src_port_id.isdigit() and snk_port_id.is_digit() + + try: + if any(not has_numeric_port_ids(*con) for con in data['connections']): + return 1 + except: + pass + return 0 diff --git a/grc/converter/main.py b/grc/converter/main.py new file mode 100644 index 0000000000..f979cc0281 --- /dev/null +++ b/grc/converter/main.py @@ -0,0 +1,163 @@ +# Copyright 2016 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 __future__ import absolute_import + +from codecs import open +import json +import logging +import os + +import six + +from . import block_tree, block + +path = os.path +logger = logging.getLogger(__name__) + +excludes = [ + 'qtgui_', + '.grc_gnuradio/', + 'blks2', + 'wxgui', + 'epy_block.xml', + 'virtual_sink.xml', + 'virtual_source.xml', + 'dummy.xml', + 'variable_struct.xml', # todo: re-implement as class + 'digital_constellation', # todo: fix template +] + + +class Converter(object): + + def __init__(self, search_path, output_dir='~/.cache/grc_gnuradio'): + self.search_path = search_path + self.output_dir = output_dir + + self._force = False + + converter_module_path = path.dirname(__file__) + self._converter_mtime = max(path.getmtime(path.join(converter_module_path, module)) + for module in os.listdir(converter_module_path) + if not module.endswith('flow_graph.py')) + + self.cache_file = os.path.join(self.output_dir, '_cache.json') + self.cache = {} + + def run(self, force=False): + self._force = force + + try: + with open(self.cache_file, encoding='utf-8') as cache_file: + self.cache = byteify(json.load(cache_file)) + except (IOError, ValueError): + self.cache = {} + self._force = True + need_cache_write = False + + if not path.isdir(self.output_dir): + os.makedirs(self.output_dir) + if self._force: + for name in os.listdir(self.output_dir): + os.remove(os.path.join(self.output_dir, name)) + + for xml_file in self.iter_files_in_block_path(): + if xml_file.endswith("block_tree.xml"): + changed = self.load_category_tree_xml(xml_file) + elif xml_file.endswith('domain.xml'): + continue + else: + changed = self.load_block_xml(xml_file) + + if changed: + need_cache_write = True + + if need_cache_write: + logger.info('Saving %d entries to json cache', len(self.cache)) + with open(self.cache_file, 'w', encoding='utf-8') as cache_file: + json.dump(self.cache, cache_file) + + def load_block_xml(self, xml_file): + """Load block description from xml file""" + if any(part in xml_file for part in excludes): + return + + block_id_from_xml = path.basename(xml_file)[:-4] + yml_file = path.join(self.output_dir, block_id_from_xml + '.block.yml') + + if not self.needs_conversion(xml_file, yml_file): + return # yml file up-to-date + + logger.info('Converting block %s', path.basename(xml_file)) + data = block.from_xml(xml_file) + if block_id_from_xml != data['id']: + logger.warning('block_id and filename differ') + self.cache[yml_file] = data + + with open(yml_file, 'w', encoding='utf-8') as yml_file: + block.dump(data, yml_file) + return True + + def load_category_tree_xml(self, xml_file): + """Validate and parse category tree file and add it to list""" + module_name = path.basename(xml_file)[:-len('block_tree.xml')].rstrip('._-') + yml_file = path.join(self.output_dir, module_name + '.tree.yml') + + if not self.needs_conversion(xml_file, yml_file): + return # yml file up-to-date + + logger.info('Converting module %s', path.basename(xml_file)) + data = block_tree.from_xml(xml_file) + self.cache[yml_file] = data + + with open(yml_file, 'w', encoding='utf-8') as yml_file: + block_tree.dump(data, yml_file) + return True + + def needs_conversion(self, source, destination): + """Check if source has already been converted and destination is up-to-date""" + if self._force or not path.exists(destination): + return True + xml_time = path.getmtime(source) + yml_time = path.getmtime(destination) + + return yml_time < xml_time or yml_time < self._converter_mtime + + def iter_files_in_block_path(self, suffix='.xml'): + """Iterator for block descriptions and category trees""" + for block_path in self.search_path: + if path.isfile(block_path): + yield block_path + elif path.isdir(block_path): + for root, _, files in os.walk(block_path, followlinks=True): + for name in files: + if name.endswith(suffix): + yield path.join(root, name) + else: + logger.warning('Invalid entry in search path: {}'.format(block_path)) + + +def byteify(data): + if isinstance(data, dict): + return {byteify(key): byteify(value) for key, value in six.iteritems(data)} + elif isinstance(data, list): + return [byteify(element) for element in data] + elif isinstance(data, unicode): + return data.encode('utf-8') + else: + return data diff --git a/grc/converter/xml.py b/grc/converter/xml.py new file mode 100644 index 0000000000..2eda786c0f --- /dev/null +++ b/grc/converter/xml.py @@ -0,0 +1,82 @@ +# Copyright 2017 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 __future__ import absolute_import, division + +import re +from os import path + +try: + # raise ImportError() + from lxml import etree + HAVE_LXML = True +except ImportError: + import xml.etree.ElementTree as etree + HAVE_LXML = False + + +_validator_cache = {None: lambda xml: True} + + +def _get_validator(dtd=None): + validator = _validator_cache.get(dtd) + if not validator: + if not path.isabs(dtd): + dtd = path.join(path.dirname(__file__), dtd) + validator = _validator_cache[dtd] = etree.DTD(dtd).validate + return validator + + +def load_lxml(filename, document_type_def=None): + """Load block description from xml file""" + + try: + xml_tree = etree.parse(filename) + _get_validator(document_type_def) + element = xml_tree.getroot() + except etree.LxmlError: + raise ValueError("Failed to parse or validate {}".format(filename)) + + version_info = {} + for inst in xml_tree.xpath('/processing-instruction()'): + if inst.target == 'grc': + version_info.update(inst.attrib) + + return element, version_info + + +def load_stdlib(filename, document_type_def=None): + """Load block description from xml file""" + + with open(filename, 'rb') as xml_file: + data = xml_file.read().decode('utf-8') + + try: + element = etree.fromstring(data) + except etree.ParseError: + raise ValueError("Failed to parse {}".format(filename)) + + version_info = {} + for body in re.findall(r'<\?(.*?)\?>', data): + element = etree.fromstring('<' + body + '/>') + if element.tag == 'grc': + version_info.update(element.attrib) + + return element, version_info + + +load = load_lxml if HAVE_LXML else load_stdlib diff --git a/grc/core/Block.py b/grc/core/Block.py deleted file mode 100644 index 087815b941..0000000000 --- a/grc/core/Block.py +++ /dev/null @@ -1,784 +0,0 @@ -""" -Copyright 2008-2015 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 __future__ import absolute_import - -import collections -import itertools -import ast - -import six -from six.moves import map, range - -from Cheetah.Template import Template - -from . import utils - -from . Constants import ( - BLOCK_FLAG_NEED_QT_GUI, - ADVANCED_PARAM_TAB, - BLOCK_FLAG_THROTTLE, BLOCK_FLAG_DISABLE_BYPASS, - BLOCK_FLAG_DEPRECATED, -) -from . Element import Element, lazy_property - - -def _get_elem(iterable, key): - items = list(iterable) - for item in items: - if item.key == key: - return item - return ValueError('Key "{}" not found in {}.'.format(key, items)) - - -class Block(Element): - - is_block = True - - STATE_LABELS = ['disabled', 'enabled', 'bypassed'] - - def __init__(self, parent, key, name, **n): - """Make a new block from nested data.""" - super(Block, self).__init__(parent) - - self.key = key - self.name = name - self.category = [cat.strip() for cat in n.get('category', '').split('/') if cat.strip()] - self.flags = n.get('flags', '') - self._doc = n.get('doc', '').strip('\n').replace('\\\n', '') - - # Backwards compatibility - if n.get('throttle') and BLOCK_FLAG_THROTTLE not in self.flags: - self.flags += BLOCK_FLAG_THROTTLE - - self._imports = [i.strip() for i in n.get('import', [])] - self._make = n.get('make') - self._var_make = n.get('var_make') - self._var_value = n.get('var_value', '$value') - self._checks = n.get('check', []) - self._callbacks = n.get('callback', []) - - self._grc_source = n.get('grc_source', '') - self.block_wrapper_path = n.get('block_wrapper_path') - - # Virtual source/sink and pad source/sink blocks are - # indistinguishable from normal GR blocks. Make explicit - # checks for them here since they have no work function or - # buffers to manage. - self.is_virtual_or_pad = self.key in ( - "virtual_source", "virtual_sink", "pad_source", "pad_sink") - self.is_variable = self.key.startswith('variable') - self.is_import = (self.key == 'import') - - # Disable blocks that are virtual/pads or variables - if self.is_virtual_or_pad or self.is_variable: - self.flags += BLOCK_FLAG_DISABLE_BYPASS - - params_n = n.get('param', []) - sources_n = n.get('source', []) - sinks_n = n.get('sink', []) - - # Get list of param tabs - self.params = collections.OrderedDict() - self._init_params( - params_n=params_n, - has_sinks=len(sinks_n), - has_sources=len(sources_n) - ) - - self.sources = self._init_ports(sources_n, direction='source') - self.sinks = self._init_ports(sinks_n, direction='sink') - self.active_sources = [] # on rewrite - self.active_sinks = [] # on rewrite - - self.states = {'_enabled': True} - - self._init_bus_ports(n) - - def _init_params(self, params_n, has_sources, has_sinks): - param_factory = self.parent_platform.get_new_param - - def add_param(key, **kwargs): - self.params[key] = param_factory(self, key=key, **kwargs) - - add_param(key='id', name='ID', type='id') - - if not (self.is_virtual_or_pad or self.is_variable or self.key == 'options'): - add_param(key='alias', name='Block Alias', type='string', - hide='part', tab=ADVANCED_PARAM_TAB) - - if not self.is_virtual_or_pad and (has_sources or has_sinks): - add_param(key='affinity', name='Core Affinity', type='int_vector', - hide='part', tab=ADVANCED_PARAM_TAB) - - if not self.is_virtual_or_pad and has_sources: - add_param(key='minoutbuf', name='Min Output Buffer', type='int', - hide='part', value='0', tab=ADVANCED_PARAM_TAB) - add_param(key='maxoutbuf', name='Max Output Buffer', type='int', - hide='part', value='0', tab=ADVANCED_PARAM_TAB) - - base_params_n = {} - for param_n in params_n: - key = param_n['key'] - if key in self.params: - raise Exception('Key "{}" already exists in params'.format(key)) - - base_key = param_n.get('base_key', None) - param_n_ext = base_params_n.get(base_key, {}).copy() - param_n_ext.update(param_n) - self.params[key] = param_factory(self, **param_n_ext) - base_params_n[key] = param_n_ext - - add_param(key='comment', name='Comment', type='_multiline', hide='part', - value='', tab=ADVANCED_PARAM_TAB) - - def _init_ports(self, ports_n, direction): - port_factory = self.parent_platform.get_new_port - ports = [] - port_keys = set() - stream_port_keys = itertools.count() - for i, port_n in enumerate(ports_n): - port_n.setdefault('key', str(next(stream_port_keys))) - port = port_factory(parent=self, direction=direction, **port_n) - key = port.key - if key in port_keys: - raise Exception('Key "{}" already exists in {}'.format(key, direction)) - port_keys.add(key) - ports.append(port) - return ports - - ############################################## - # validation and rewrite - ############################################## - def rewrite(self): - """ - Add and remove ports to adjust for the nports. - """ - Element.rewrite(self) - - def rekey(ports): - """Renumber non-message/message ports""" - domain_specific_port_index = collections.defaultdict(int) - for port in [p for p in ports if p.key.isdigit()]: - domain = port.domain - port.key = str(domain_specific_port_index[domain]) - domain_specific_port_index[domain] += 1 - - # Adjust nports - for ports in (self.sources, self.sinks): - self._rewrite_nports(ports) - self.back_ofthe_bus(ports) - rekey(ports) - - self._rewrite_bus_ports() - - # disconnect hidden ports - for port in itertools.chain(self.sources, self.sinks): - if port.get_hide(): - for connection in port.get_connections(): - self.parent_flowgraph.remove_element(connection) - - - self.active_sources = [p for p in self.get_sources_gui() if not p.get_hide()] - self.active_sinks = [p for p in self.get_sinks_gui() if not p.get_hide()] - - def _rewrite_nports(self, ports): - for port in ports: - if port.is_clone: # Not a master port and no left-over clones - continue - nports = port.get_nports() or 1 - for clone in port.clones[nports-1:]: - # Remove excess connections - for connection in clone.get_connections(): - self.parent_flowgraph.remove_element(connection) - port.remove_clone(clone) - ports.remove(clone) - # Add more cloned ports - for j in range(1 + len(port.clones), nports): - clone = port.add_clone() - ports.insert(ports.index(port) + j, clone) - - def validate(self): - """ - Validate this block. - Call the base class validate. - Evaluate the checks: each check must evaluate to True. - """ - Element.validate(self) - self._run_checks() - self._validate_generate_mode_compat() - self._validate_var_value() - - def _run_checks(self): - """Evaluate the checks""" - for check in self._checks: - check_res = self.resolve_dependencies(check) - try: - if not self.parent.evaluate(check_res): - self.add_error_message('Check "{}" failed.'.format(check)) - except: - self.add_error_message('Check "{}" did not evaluate.'.format(check)) - - def _validate_generate_mode_compat(self): - """check if this is a GUI block and matches the selected generate option""" - current_generate_option = self.parent.get_option('generate_options') - - def check_generate_mode(label, flag, valid_options): - block_requires_mode = ( - flag in self.flags or self.name.upper().startswith(label) - ) - if block_requires_mode and current_generate_option not in valid_options: - self.add_error_message("Can't generate this block in mode: {} ".format( - repr(current_generate_option))) - - check_generate_mode('QT GUI', BLOCK_FLAG_NEED_QT_GUI, ('qt_gui', 'hb_qt_gui')) - - def _validate_var_value(self): - """or variables check the value (only if var_value is used)""" - if self.is_variable and self._var_value != '$value': - value = self._var_value - try: - value = self.get_var_value() - self.parent.evaluate(value) - except Exception as err: - self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(value, err)) - - ############################################## - # props - ############################################## - - @lazy_property - def is_throtteling(self): - return BLOCK_FLAG_THROTTLE in self.flags - - @lazy_property - def is_deprecated(self): - return BLOCK_FLAG_DEPRECATED in self.flags - - @property - def documentation(self): - documentation = self.parent_platform.block_docstrings.get(self.key, {}) - from_xml = self._doc.strip() - if from_xml: - documentation[''] = from_xml - return documentation - - @property - def comment(self): - return self.params['comment'].get_value() - - @property - def state(self): - """Gets the block's current state.""" - try: - return self.STATE_LABELS[int(self.states['_enabled'])] - except ValueError: - return 'enabled' - - @state.setter - def state(self, value): - """Sets the state for the block.""" - try: - encoded = self.STATE_LABELS.index(value) - except ValueError: - encoded = 1 - self.states['_enabled'] = encoded - - # Enable/Disable Aliases - @property - def enabled(self): - """Get the enabled state of the block""" - return self.state != 'disabled' - - ############################################## - # Getters (old) - ############################################## - - def get_imports(self, raw=False): - """ - Resolve all import statements. - Split each import statement at newlines. - Combine all import statements into a list. - Filter empty imports. - - Returns: - a list of import statements - """ - if raw: - return self._imports - return [i for i in sum((self.resolve_dependencies(i).split('\n') - for i in self._imports), []) if i] - - def get_make(self, raw=False): - if raw: - return self._make - return self.resolve_dependencies(self._make) - - def get_var_make(self): - return self.resolve_dependencies(self._var_make) - - def get_var_value(self): - return self.resolve_dependencies(self._var_value) - - def get_callbacks(self): - """ - Get a list of function callbacks for this block. - - Returns: - a list of strings - """ - def make_callback(callback): - callback = self.resolve_dependencies(callback) - if 'self.' in callback: - return callback - return 'self.{}.{}'.format(self.get_id(), callback) - return [make_callback(c) for c in self._callbacks] - - def is_virtual_sink(self): - return self.key == 'virtual_sink' - - def is_virtual_source(self): - return self.key == 'virtual_source' - - # Block bypassing - def get_bypassed(self): - """ - Check if the block is bypassed - """ - return self.state == 'bypassed' - - def set_bypassed(self): - """ - Bypass the block - - Returns: - True if block chagnes state - """ - if self.state != 'bypassed' and self.can_bypass(): - self.state = 'bypassed' - return True - return False - - def can_bypass(self): - """ Check the number of sinks and sources and see if this block can be bypassed """ - # Check to make sure this is a single path block - # Could possibly support 1 to many blocks - if len(self.sources) != 1 or len(self.sinks) != 1: - return False - if not (self.sources[0].get_type() == self.sinks[0].get_type()): - return False - if BLOCK_FLAG_DISABLE_BYPASS in self.flags: - return False - return True - - def __str__(self): - return 'Block - {} - {}({})'.format(self.get_id(), self.name, self.key) - - def get_id(self): - return self.params['id'].get_value() - - def get_ports(self): - return self.sources + self.sinks - - def get_ports_gui(self): - return self.get_sources_gui() + self.get_sinks_gui() - - def active_ports(self): - return itertools.chain(self.active_sources, self.active_sinks) - - def get_children(self): - return self.get_ports() + list(self.params.values()) - - def get_children_gui(self): - return self.get_ports_gui() + self.params.values() - - ############################################## - # Access - ############################################## - - def get_param(self, key): - return self.params[key] - - def get_sink(self, key): - return _get_elem(self.sinks, key) - - def get_sinks_gui(self): - return self.filter_bus_port(self.sinks) - - def get_source(self, key): - return _get_elem(self.sources, key) - - def get_sources_gui(self): - return self.filter_bus_port(self.sources) - - def get_connections(self): - return sum((port.get_connections() for port in self.get_ports()), []) - - ############################################## - # Resolve - ############################################## - def resolve_dependencies(self, tmpl): - """ - Resolve a paramater dependency with cheetah templates. - - Args: - tmpl: the string with dependencies - - Returns: - the resolved value - """ - tmpl = str(tmpl) - if '$' not in tmpl: - return tmpl - # TODO: cache that - n = {key: param.template_arg for key, param in six.iteritems(self.params)} - try: - return str(Template(tmpl, n)) - except Exception as err: - return "Template error: {}\n {}".format(tmpl, err) - - ############################################## - # Import/Export Methods - ############################################## - def export_data(self): - """ - Export this block's params to nested data. - - Returns: - a nested data odict - """ - n = collections.OrderedDict() - n['key'] = self.key - - params = (param.export_data() for param in six.itervalues(self.params)) - states = (collections.OrderedDict([('key', key), ('value', repr(value))]) - for key, value in six.iteritems(self.states)) - n['param'] = sorted(itertools.chain(states, params), key=lambda p: p['key']) - - if any('bus' in a.get_type() for a in self.sinks): - n['bus_sink'] = '1' - if any('bus' in a.get_type() for a in self.sources): - n['bus_source'] = '1' - 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. - Since params can be dynamically created based another param, - call rewrite, and repeat the load until the params stick. - This call to rewrite will also create any dynamic ports - that are needed for the connections creation phase. - - Args: - n: the nested data odict - """ - param_data = {p['key']: p['value'] for p in n.get('param', [])} - - for key in self.states: - try: - self.states[key] = ast.literal_eval(param_data.pop(key)) - except (KeyError, SyntaxError, ValueError): - pass - - def get_hash(): - return hash(tuple(hash(v) for v in self.params.values())) - - pre_rewrite_hash = -1 - while pre_rewrite_hash != get_hash(): - for key, value in six.iteritems(param_data): - try: - self.params[key].set_value(value) - except KeyError: - continue - # Store hash and call rewrite - pre_rewrite_hash = get_hash() - self.rewrite() - - self._import_bus_stuff(n) - - ############################################## - # Bus ports stuff - ############################################## - - def get_bus_structure(self, direction): - bus_structure = self.resolve_dependencies(self._bus_structure[direction]) - if not bus_structure: - return - try: - return self.parent_flowgraph.evaluate(bus_structure) - except: - return - - @staticmethod - def back_ofthe_bus(portlist): - portlist.sort(key=lambda p: p._type == 'bus') - - @staticmethod - def filter_bus_port(ports): - buslist = [p for p in ports if p._type == 'bus'] - return buslist or ports - - def _import_bus_stuff(self, n): - bus_sinks = n.get('bus_sink', []) - if len(bus_sinks) > 0 and not self._bussify_sink: - self.bussify('sink') - elif len(bus_sinks) > 0: - self.bussify('sink') - self.bussify('sink') - bus_sources = n.get('bus_source', []) - if len(bus_sources) > 0 and not self._bussify_source: - self.bussify('source') - elif len(bus_sources) > 0: - self.bussify('source') - self.bussify('source') - - def form_bus_structure(self, direc): - ports = self.sources if direc == 'source' else self.sinks - struct = self.get_bus_structure(direc) - - if not struct: - struct = [list(range(len(ports)))] - - elif any(isinstance(p.get_nports(), int) for p in ports): - last = 0 - structlet = [] - for port in ports: - nports = port.get_nports() - if not isinstance(nports, int): - continue - structlet.extend(a + last for a in range(nports)) - last += nports - struct = [structlet] - - self.current_bus_structure[direc] = struct - return struct - - def bussify(self, direc): - ports = self.sources if direc == 'source' else self.sinks - - for elt in ports: - for connect in elt.get_connections(): - self.parent.remove_element(connect) - - if ports and all('bus' != p.get_type() for p in ports): - struct = self.current_bus_structure[direc] = self.form_bus_structure(direc) - n = {'type': 'bus'} - if ports[0].get_nports(): - n['nports'] = '1' - - for i, structlet in enumerate(struct): - name = 'bus{}#{}'.format(i, len(structlet)) - port = self.parent_platform.get_new_port( - self, direction=direc, key=str(len(ports)), name=name, **n) - ports.append(port) - elif any('bus' == p.get_type() for p in ports): - get_p_gui = self.get_sources_gui if direc == 'source' else self.get_sinks_gui - for elt in get_p_gui(): - ports.remove(elt) - self.current_bus_structure[direc] = '' - - def _init_bus_ports(self, n): - self.current_bus_structure = {'source': '', 'sink': ''} - self._bus_structure = {'source': n.get('bus_structure_source', ''), - 'sink': n.get('bus_structure_sink', '')} - self._bussify_sink = n.get('bus_sink') - self._bussify_source = n.get('bus_source') - if self._bussify_sink: - self.bussify('sink') - if self._bussify_source: - self.bussify('source') - - def _rewrite_bus_ports(self): - return # fixme: probably broken - - def doit(ports, ports_gui, direc): - if not self.current_bus_structure[direc]: - return - - bus_structure = self.form_bus_structure(direc) - for port in ports_gui[len(bus_structure):]: - for connect in port.get_connections(): - self.parent_flowgraph.remove_element(connect) - ports.remove(port) - - port_factory = self.parent_platform.get_new_port - - if len(ports_gui) < len(bus_structure): - for i in range(len(ports_gui), len(bus_structure)): - port = port_factory(self, direction=direc, key=str(1 + i), - name='bus', type='bus') - ports.append(port) - - doit(self.sources, self.get_sources_gui(), 'source') - doit(self.sinks, self.get_sinks_gui(), 'sink') - - if 'bus' in [a.get_type() for a in self.get_sources_gui()]: - for i in range(len(self.get_sources_gui())): - if not self.get_sources_gui()[i].get_connections(): - continue - source = self.get_sources_gui()[i] - sink = [] - - for j in range(len(source.get_connections())): - sink.append(source.get_connections()[j].sink_port) - for elt in source.get_connections(): - self.parent_flowgraph.remove_element(elt) - for j in sink: - self.parent_flowgraph.connect(source, j) - - -class EPyBlock(Block): - - def __init__(self, flow_graph, **n): - super(EPyBlock, self).__init__(flow_graph, **n) - self._epy_source_hash = -1 # for epy blocks - self._epy_reload_error = None - - def rewrite(self): - Element.rewrite(self) - - param_blk = self.params['_io_cache'] - param_src = self.params['_source_code'] - - src = param_src.get_value() - src_hash = hash((self.get_id(), src)) - if src_hash == self._epy_source_hash: - return - - try: - blk_io = utils.epy_block_io.extract(src) - - except Exception as e: - self._epy_reload_error = ValueError(str(e)) - try: # Load last working block io - blk_io_args = eval(param_blk.get_value()) - if len(blk_io_args) == 6: - blk_io_args += ([],) # add empty callbacks - blk_io = utils.epy_block_io.BlockIO(*blk_io_args) - except Exception: - return - else: - self._epy_reload_error = None # Clear previous errors - param_blk.set_value(repr(tuple(blk_io))) - - # print "Rewriting embedded python block {!r}".format(self.get_id()) - - self._epy_source_hash = src_hash - self.name = blk_io.name or blk_io.cls - self._doc = blk_io.doc - self._imports[0] = 'import ' + self.get_id() - self._make = '{0}.{1}({2})'.format(self.get_id(), blk_io.cls, ', '.join( - '{0}=${{ {0} }}'.format(key) for key, _ in blk_io.params)) - self._callbacks = ['{0} = ${{ {0} }}'.format(attr) for attr in blk_io.callbacks] - self._update_params(blk_io.params) - self._update_ports('in', self.sinks, blk_io.sinks, 'sink') - self._update_ports('out', self.sources, blk_io.sources, 'source') - - super(EPyBlock, self).rewrite() - - def _update_params(self, params_in_src): - param_factory = self.parent_platform.get_new_param - params = {} - for param in list(self.params): - if hasattr(param, '__epy_param__'): - params[param.key] = param - del self.params[param.key] - - for key, value in params_in_src: - try: - param = params[key] - if param.default == param.value: - param.set_value(value) - param.default = str(value) - except KeyError: # need to make a new param - param = param_factory( - parent=self, key=key, type='raw', value=value, - name=key.replace('_', ' ').title(), - ) - setattr(param, '__epy_param__', True) - self.params[key] = param - - def _update_ports(self, label, ports, port_specs, direction): - port_factory = self.parent_platform.get_new_port - ports_to_remove = list(ports) - iter_ports = iter(ports) - ports_new = [] - port_current = next(iter_ports, None) - for key, port_type, vlen in port_specs: - reuse_port = ( - port_current is not None and - port_current.get_type() == port_type and - port_current.get_vlen() == vlen and - (key.isdigit() or port_current.key == key) - ) - if reuse_port: - ports_to_remove.remove(port_current) - port, port_current = port_current, next(iter_ports, None) - else: - n = dict(name=label + str(key), type=port_type, key=key) - if port_type == 'message': - n['name'] = key - n['optional'] = '1' - if vlen > 1: - n['vlen'] = str(vlen) - port = port_factory(self, direction=direction, **n) - ports_new.append(port) - # replace old port list with new one - del ports[:] - ports.extend(ports_new) - # remove excess port connections - for port in ports_to_remove: - for connection in port.get_connections(): - self.parent_flowgraph.remove_element(connection) - - def validate(self): - super(EPyBlock, self).validate() - if self._epy_reload_error: - self.params['_source_code'].add_error_message(str(self._epy_reload_error)) - - -class DummyBlock(Block): - - is_dummy_block = True - build_in_param_keys = 'id alias affinity minoutbuf maxoutbuf comment' - - def __init__(self, parent, key, missing_key, params_n): - super(DummyBlock, self).__init__(parent=parent, key=missing_key, name='Missing Block') - param_factory = self.parent_platform.get_new_param - for param_n in params_n: - key = param_n['key'] - self.params.setdefault(key, param_factory(self, key=key, name=key, type='string')) - - def is_valid(self): - return False - - @property - def enabled(self): - return False - - def add_missing_port(self, key, dir): - port = self.parent_platform.get_new_port( - parent=self, direction=dir, key=key, name='?', type='', - ) - if port.is_source: - self.sources.append(port) - else: - self.sinks.append(port) - return port diff --git a/grc/core/Config.py b/grc/core/Config.py index cc199a348f..eb53e1751d 100644 --- a/grc/core/Config.py +++ b/grc/core/Config.py @@ -1,5 +1,4 @@ -""" -Copyright 2016 Free Software Foundation, Inc. +"""Copyright 2016 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -32,6 +31,8 @@ class Config(object): hier_block_lib_dir = os.environ.get('GRC_HIER_PATH', Constants.DEFAULT_HIER_BLOCK_LIB_DIR) + yml_block_cache = os.path.expanduser('~/.cache/grc_gnuradio') # FIXME: remove this as soon as converter is stable + def __init__(self, version, version_parts=None, name=None, prefs=None): self._gr_prefs = prefs if prefs else DummyPrefs() self.version = version @@ -39,6 +40,9 @@ class Config(object): if name: self.name = name + if not os.path.exists(self.yml_block_cache): + os.mkdir(self.yml_block_cache) + @property def block_paths(self): path_list_sep = {'/': ':', '\\': ';'}[os.path.sep] @@ -46,6 +50,7 @@ class Config(object): paths_sources = ( self.hier_block_lib_dir, os.environ.get('GRC_BLOCKS_PATH', ''), + self.yml_block_cache, self._gr_prefs.get_string('grc', 'local_blocks_path', ''), self._gr_prefs.get_string('grc', 'global_blocks_path', ''), ) diff --git a/grc/core/Connection.py b/grc/core/Connection.py index 066532149b..01baaaf8fc 100644 --- a/grc/core/Connection.py +++ b/grc/core/Connection.py @@ -19,26 +19,22 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import -import collections - -from six.moves import range - -from . import Constants -from .Element import Element, lazy_property +from .base import Element +from .utils.descriptors import lazy_property class Connection(Element): is_connection = True - def __init__(self, parent, porta, portb): + def __init__(self, parent, source, sink): """ Make a new connection given the parent and 2 ports. Args: flow_graph: the parent of this element - porta: a port (any direction) - portb: a port (any direction) + source: a port (any direction) + sink: a port (any direction) @throws Error cannot make connection Returns: @@ -46,37 +42,31 @@ class Connection(Element): """ Element.__init__(self, parent) - source, sink = self._get_sink_source(porta, portb) + if not source.is_source: + source, sink = sink, source + if not source.is_source: + raise ValueError('Connection could not isolate source') + if not sink.is_sink: + raise ValueError('Connection could not isolate sink') self.source_port = source self.sink_port = sink - # Ensure that this connection (source -> sink) is unique - if self in self.parent_flowgraph.connections: - raise LookupError('This connection between source and sink is not unique.') - - if self.is_bus(): - self._make_bus_connect() + def __str__(self): + return 'Connection (\n\t{}\n\t\t{}\n\t{}\n\t\t{}\n)'.format( + self.source_block, self.source_port, self.sink_block, self.sink_port, + ) def __eq__(self, other): if not isinstance(other, self.__class__): return NotImplemented return self.source_port == other.source_port and self.sink_port == other.sink_port - @staticmethod - def _get_sink_source(porta, portb): - 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 - if not source: - raise ValueError('Connection could not isolate source') - if not sink: - raise ValueError('Connection could not isolate sink') - return source, sink + def __hash__(self): + return hash((self.source_port, self.sink_port)) + + def __iter__(self): + return iter((self.source_port, self.sink_port)) @lazy_property def source_block(self): @@ -86,6 +76,10 @@ class Connection(Element): def sink_block(self): return self.sink_port.parent_block + @lazy_property + def type(self): + return self.source_port.domain, self.sink_port.domain + @property def enabled(self): """ @@ -96,14 +90,6 @@ class Connection(Element): """ return self.source_block.enabled and self.sink_block.enabled - def __str__(self): - return 'Connection (\n\t{}\n\t\t{}\n\t{}\n\t\t{}\n)'.format( - self.source_block, self.source_port, self.sink_block, self.sink_port, - ) - - def is_bus(self): - return self.source_port.get_type() == 'bus' - def validate(self): """ Validate the connections. @@ -112,29 +98,12 @@ class Connection(Element): Element.validate(self) platform = self.parent_platform - source_domain = self.source_port.domain - sink_domain = self.sink_port.domain + if self.type not in platform.connection_templates: + self.add_error_message('No connection known between domains "{}" and "{}"' + ''.format(*self.type)) - if (source_domain, sink_domain) not in platform.connection_templates: - self.add_error_message('No connection known for domains "{}", "{}"'.format( - source_domain, sink_domain)) - too_many_other_sinks = ( - not platform.domains.get(source_domain, []).get('multiple_sinks', False) and - len(self.source_port.get_enabled_connections()) > 1 - ) - too_many_other_sources = ( - not platform.domains.get(sink_domain, []).get('multiple_sources', False) and - len(self.sink_port.get_enabled_connections()) > 1 - ) - if too_many_other_sinks: - self.add_error_message( - 'Domain "{}" can have only one downstream block'.format(source_domain)) - if too_many_other_sources: - self.add_error_message( - 'Domain "{}" can have only one upstream block'.format(sink_domain)) - - source_size = Constants.TYPE_TO_SIZEOF[self.source_port.get_type()] * self.source_port.get_vlen() - sink_size = Constants.TYPE_TO_SIZEOF[self.sink_port.get_type()] * self.sink_port.get_vlen() + source_size = self.source_port.item_size + sink_size = self.sink_port.item_size if source_size != sink_size: self.add_error_message('Source IO size "{}" does not match sink IO size "{}".'.format(source_size, sink_size)) @@ -148,23 +117,7 @@ class Connection(Element): Returns: a nested data odict """ - n = collections.OrderedDict() - n['source_block_id'] = self.source_block.get_id() - n['sink_block_id'] = self.sink_block.get_id() - n['source_key'] = self.source_port.key - n['sink_key'] = self.sink_port.key - return n - - def _make_bus_connect(self): - source, sink = self.source_port, self.sink_port - - if source.get_type() == sink.get_type() == 'bus': - raise ValueError('busses must get with busses') - - sources = source.get_associated_ports() - sinks = sink.get_associated_ports() - if len(sources) != len(sinks): - raise ValueError('port connections must have same cardinality') - - for ports in zip(sources, sinks): - self.parent_flowgraph.connect(*ports) + return ( + self.source_block.name, self.source_port.key, + self.sink_block.name, self.sink_port.key + ) diff --git a/grc/core/Constants.py b/grc/core/Constants.py index caf301be60..fc5383378c 100644 --- a/grc/core/Constants.py +++ b/grc/core/Constants.py @@ -23,17 +23,15 @@ import os import stat import numpy -import six + # Data files DATA_DIR = os.path.dirname(__file__) -FLOW_GRAPH_DTD = os.path.join(DATA_DIR, 'flow_graph.dtd') -BLOCK_TREE_DTD = os.path.join(DATA_DIR, 'block_tree.dtd') BLOCK_DTD = os.path.join(DATA_DIR, 'block.dtd') DEFAULT_FLOW_GRAPH = os.path.join(DATA_DIR, 'default_flow_graph.grc') DEFAULT_HIER_BLOCK_LIB_DIR = os.path.expanduser('~/.grc_gnuradio') -DOMAIN_DTD = os.path.join(DATA_DIR, 'domain.dtd') +BLOCK_DESCRIPTION_FILE_FORMAT_VERSION = 1 # File format versions: # 0: undefined / legacy # 1: non-numeric message port keys (label is used instead) @@ -45,15 +43,10 @@ ADVANCED_PARAM_TAB = "Advanced" DEFAULT_BLOCK_MODULE_NAME = '(no module specified)' # Port domains -GR_STREAM_DOMAIN = "gr_stream" -GR_MESSAGE_DOMAIN = "gr_message" +GR_STREAM_DOMAIN = "stream" +GR_MESSAGE_DOMAIN = "message" DEFAULT_DOMAIN = GR_STREAM_DOMAIN -BLOCK_FLAG_THROTTLE = 'throttle' -BLOCK_FLAG_DISABLE_BYPASS = 'disable_bypass' -BLOCK_FLAG_NEED_QT_GUI = 'need_qt_gui' -BLOCK_FLAG_DEPRECATED = 'deprecated' - # File creation modes TOP_BLOCK_FILE_MODE = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | \ stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH diff --git a/grc/core/FlowGraph.py b/grc/core/FlowGraph.py index bf5bf6d93e..3ad95eb207 100644 --- a/grc/core/FlowGraph.py +++ b/grc/core/FlowGraph.py @@ -17,24 +17,17 @@ from __future__ import absolute_import, print_function -import imp -import time -import re -from operator import methodcaller import collections +import imp +import itertools import sys +from operator import methodcaller, attrgetter -from . import Messages +from . import Messages, blocks from .Constants import FLOW_GRAPH_FILE_FORMAT_VERSION -from .Element import Element -from .utils import expr_utils, shlex - -_parameter_matcher = re.compile('^(parameter)$') -_monitors_searcher = re.compile('(ctrlport_monitor)') -_bussink_searcher = re.compile('^(bus_sink)$') -_bussrc_searcher = re.compile('^(bus_source)$') -_bus_struct_sink_searcher = re.compile('^(bus_structure_sink)$') -_bus_struct_src_searcher = re.compile('^(bus_structure_source)$') +from .base import Element +from .utils import expr_utils +from .utils.backports import shlex class FlowGraph(Element): @@ -52,11 +45,10 @@ class FlowGraph(Element): the flow graph object """ Element.__init__(self, parent) - self._timestamp = time.ctime() - self._options_block = self.parent_platform.get_new_block(self, 'options') + self._options_block = self.parent_platform.make_block(self, 'options') self.blocks = [self._options_block] - self.connections = [] + self.connections = set() self._eval_cache = {} self.namespace = {} @@ -66,15 +58,14 @@ class FlowGraph(Element): def __str__(self): return 'FlowGraph - {}({})'.format(self.get_option('title'), self.get_option('id')) - def get_imports(self): + def imports(self): """ Get a set of all import statements in this flow graph namespace. Returns: - a set of import statements + a list of import statements """ - imports = sum([block.get_imports() for block in self.iter_enabled_blocks()], []) - return sorted(set(imports)) + return [block.templates.render('imports') for block in self.iter_enabled_blocks()] def get_variables(self): """ @@ -85,7 +76,7 @@ class FlowGraph(Element): a sorted list of variable blocks in order of dependency (indep -> dep) """ variables = [block for block in self.iter_enabled_blocks() if block.is_variable] - return expr_utils.sort_objects(variables, methodcaller('get_id'), methodcaller('get_var_make')) + return expr_utils.sort_objects(variables, attrgetter('name'), methodcaller('get_var_make')) def get_parameters(self): """ @@ -94,47 +85,21 @@ class FlowGraph(Element): Returns: a list of parameterized variables """ - parameters = [b for b in self.iter_enabled_blocks() if _parameter_matcher.match(b.key)] + parameters = [b for b in self.iter_enabled_blocks() if b.key == 'parameter'] return parameters def get_monitors(self): """ Get a list of all ControlPort monitors """ - monitors = [b for b in self.iter_enabled_blocks() if _monitors_searcher.search(b.key)] + monitors = [b for b in self.iter_enabled_blocks() if 'ctrlport_monitor' in b.key] return monitors def get_python_modules(self): """Iterate over custom code block ID and Source""" for block in self.iter_enabled_blocks(): if block.key == 'epy_module': - yield block.get_id(), block.get_param('source_code').get_value() - - def get_bussink(self): - bussink = [b for b in self.get_enabled_blocks() if _bussink_searcher.search(b.key)] - - for i in bussink: - for j in i.params.values(): - if j.name == 'On/Off' and j.get_value() == 'on': - return True - return False - - def get_bussrc(self): - bussrc = [b for b in self.get_enabled_blocks() if _bussrc_searcher.search(b.key)] - - for i in bussrc: - for j in i.params.values(): - if j.name == 'On/Off' and j.get_value() == 'on': - return True - return False - - def get_bus_structure_sink(self): - bussink = [b for b in self.get_enabled_blocks() if _bus_struct_sink_searcher.search(b.key)] - return bussink - - def get_bus_structure_src(self): - bussrc = [b for b in self.get_enabled_blocks() if _bus_struct_src_searcher.search(b.key)] - return bussrc + yield block.name, block.params[1].get_value() def iter_enabled_blocks(self): """ @@ -180,7 +145,7 @@ class FlowGraph(Element): Returns: the value held by that param """ - return self._options_block.get_param(key).get_evaluated() + return self._options_block.params[key].get_evaluated() def get_run_command(self, file_path, split=False): run_command = self.get_option('run_command') @@ -195,16 +160,19 @@ class FlowGraph(Element): ############################################## # Access Elements ############################################## - def get_block(self, id): + def get_block(self, name): for block in self.blocks: - if block.get_id() == id: + if block.name == name: return block - raise KeyError('No block with ID {!r}'.format(id)) + raise KeyError('No block with name {!r}'.format(name)) def get_elements(self): - return self.blocks + self.connections + elements = list(self.blocks) + elements.extend(self.connections) + return elements - get_children = get_elements + def children(self): + return itertools.chain(self.blocks, self.connections) def rewrite(self): """ @@ -216,7 +184,7 @@ class FlowGraph(Element): def renew_namespace(self): namespace = {} # Load imports - for expr in self.get_imports(): + for expr in self.imports(): try: exec(expr, namespace) except: @@ -232,19 +200,19 @@ class FlowGraph(Element): # Load parameters np = {} # params don't know each other - for parameter in self.get_parameters(): + for parameter_block in self.get_parameters(): try: - value = eval(parameter.get_param('value').to_code(), namespace) - np[parameter.get_id()] = value + value = eval(parameter_block.params['value'].to_code(), namespace) + np[parameter_block.name] = value except: pass namespace.update(np) # Merge param namespace # Load variables - for variable in self.get_variables(): + for variable_block in self.get_variables(): try: - value = eval(variable.get_var_value(), namespace) - namespace[variable.get_id()] = value + value = eval(variable_block.value, namespace, variable_block.namespace) + namespace[variable_block.name] = value except: pass @@ -252,41 +220,37 @@ class FlowGraph(Element): self._eval_cache.clear() self.namespace.update(namespace) - def evaluate(self, expr): + def evaluate(self, expr, namespace=None, local_namespace=None): """ Evaluate the expression. - - Args: - expr: the string expression - @throw Exception bad expression - - Returns: - the evaluated data """ # Evaluate if not expr: raise Exception('Cannot evaluate empty statement.') - return self._eval_cache.setdefault(expr, eval(expr, self.namespace)) + if namespace is not None: + return eval(expr, namespace, local_namespace) + else: + return self._eval_cache.setdefault(expr, eval(expr, self.namespace)) ############################################## # Add/remove stuff ############################################## - def new_block(self, key, **kwargs): + def new_block(self, block_id, **kwargs): """ Get a new block of the specified key. Add the block to the list of elements. Args: - key: the block key + block_id: the block key Returns: the new block or None if not found """ - if key == 'options': + if block_id == 'options': return self._options_block try: - block = self.parent_platform.get_new_block(self, key, **kwargs) + block = self.parent_platform.make_block(self, block_id, **kwargs) self.blocks.append(block) except KeyError: block = None @@ -304,12 +268,17 @@ class FlowGraph(Element): Returns: the new connection """ - connection = self.parent_platform.Connection( - parent=self, porta=porta, portb=portb) - self.connections.append(connection) + parent=self, source=porta, sink=portb) + self.connections.add(connection) return connection + def disconnect(self, *ports): + to_be_removed = [con for con in self.connections + if any(port in con for port in ports)] + for con in to_be_removed: + self.remove_element(con) + def remove_element(self, element): """ Remove the element from the list of elements. @@ -321,21 +290,14 @@ class FlowGraph(Element): return if element.is_port: - # Found a port, set to parent signal block - element = element.parent + element = element.parent_block # remove parent block if element in self.blocks: # Remove block, remove all involved connections - for port in element.get_ports(): - for connection in port.get_connections(): - self.remove_element(connection) + self.disconnect(*element.ports()) self.blocks.remove(element) elif element in self.connections: - if element.is_bus(): - for port in element.source_port.get_associated_ports(): - for connection in port.get_connections(): - self.remove_element(connection) self.connections.remove(element) ############################################## @@ -349,70 +311,61 @@ class FlowGraph(Element): Returns: a nested data odict """ - # sort blocks and connections for nicer diffs - blocks = sorted(self.blocks, key=lambda b: ( - b.key != 'options', # options to the front - not b.key.startswith('variable'), # then vars - str(b) - )) - connections = sorted(self.connections, key=str) - n = collections.OrderedDict() - n['timestamp'] = self._timestamp - n['block'] = [b.export_data() for b in blocks] - n['connection'] = [c.export_data() for c in connections] - instructions = collections.OrderedDict() - instructions['created'] = '.'.join(self.parent.config.version_parts) - instructions['format'] = FLOW_GRAPH_FILE_FORMAT_VERSION - return {'flow_graph': n, '_instructions': instructions} - - def import_data(self, n): + def block_order(b): + return not b.key.startswith('variable'), b.name # todo: vars still first ?!? + + data = collections.OrderedDict() + data['options'] = self._options_block.export_data() + data['blocks'] = [b.export_data() for b in sorted(self.blocks, key=block_order) + if b is not self._options_block] + data['connections'] = sorted(c.export_data() for c in self.connections) + data['metadata'] = {'file_format': FLOW_GRAPH_FILE_FORMAT_VERSION} + return data + + def _build_depending_hier_block(self, block_id): + # we're before the initial fg update(), so no evaluated values! + # --> use raw value instead + path_param = self._options_block.params['hier_block_src_path'] + file_path = self.parent_platform.find_file_in_paths( + filename=block_id + '.grc', + paths=path_param.get_value(), + cwd=self.grc_file_path + ) + if file_path: # grc file found. load and get block + self.parent_platform.load_and_generate_flow_graph(file_path, hier_only=True) + return self.new_block(block_id) # can be None + + def import_data(self, data): """ Import blocks and connections into this flow graph. Clear this flow graph of all previous blocks and connections. Any blocks or connections in error will be ignored. Args: - n: the nested data odict + data: the nested data odict """ # Remove previous elements del self.blocks[:] - del self.connections[:] - # set file format - try: - instructions = n.get('_instructions', {}) - file_format = int(instructions.get('format', '0')) or _guess_file_format_1(n) - except: - file_format = 0 + self.connections.clear() - fg_n = n and n.get('flow_graph', {}) # use blank data if none provided - self._timestamp = fg_n.get('timestamp', time.ctime()) + file_format = data['metadata']['file_format'] # build the blocks + self._options_block.import_data(name='', **data.get('options', {})) self.blocks.append(self._options_block) - for block_n in fg_n.get('block', []): - key = block_n['key'] - block = self.new_block(key) - - if not block: - # we're before the initial fg update(), so no evaluated values! - # --> use raw value instead - path_param = self._options_block.get_param('hier_block_src_path') - file_path = self.parent_platform.find_file_in_paths( - filename=key + '.grc', - paths=path_param.get_value(), - cwd=self.grc_file_path - ) - if file_path: # grc file found. load and get block - self.parent_platform.load_and_generate_flow_graph(file_path, hier_only=True) - block = self.new_block(key) # can be None - - if not block: # looks like this block key cannot be found - # create a dummy block instead - block = self.new_block('_dummy', missing_key=key, - params_n=block_n.get('param', [])) - print('Block key "%s" not found' % key) - - block.import_data(block_n) + + for block_data in data.get('blocks', []): + block_id = block_data['id'] + block = ( + self.new_block(block_id) or + self._build_depending_hier_block(block_id) or + self.new_block(block_id='_dummy', missing_block_id=block_id, **block_data) + ) + + if isinstance(block, blocks.DummyBlock): + print('Block id "%s" not found' % block_id) + + block.import_data(**block_data) self.rewrite() # evaluate stuff like nports before adding connections @@ -420,9 +373,9 @@ class FlowGraph(Element): def verify_and_get_port(key, block, dir): ports = block.sinks if dir == 'sink' else block.sources for port in ports: - if key == port.key: + if key == port.key or key + '0' == port.key: break - if not key.isdigit() and port.get_type() == '' and key == port.name: + if not key.isdigit() and port.dtype == '' and key == port.name: break else: if block.is_dummy_block: @@ -431,34 +384,32 @@ class FlowGraph(Element): raise LookupError('%s key %r not in %s block keys' % (dir, key, dir)) return port - errors = False - for connection_n in fg_n.get('connection', []): - # get the block ids and port keys - source_block_id = connection_n.get('source_block_id') - sink_block_id = connection_n.get('sink_block_id') - source_key = connection_n.get('source_key') - sink_key = connection_n.get('sink_key') + had_connect_errors = False + _blocks = {block.name: block for block in self.blocks} + for src_blk_id, src_port_id, snk_blk_id, snk_port_id in data.get('connections', []): try: - source_block = self.get_block(source_block_id) - sink_block = self.get_block(sink_block_id) + source_block = _blocks[src_blk_id] + sink_block = _blocks[snk_blk_id] # fix old, numeric message ports keys if file_format < 1: - source_key, sink_key = _update_old_message_port_keys( - source_key, sink_key, source_block, sink_block) + src_port_id, snk_port_id = _update_old_message_port_keys( + src_port_id, snk_port_id, source_block, sink_block) # build the connection - source_port = verify_and_get_port(source_key, source_block, 'source') - sink_port = verify_and_get_port(sink_key, sink_block, 'sink') + source_port = verify_and_get_port(src_port_id, source_block, 'source') + sink_port = verify_and_get_port(snk_port_id, sink_block, 'sink') + self.connect(source_port, sink_port) - except LookupError as e: + + except (KeyError, LookupError) as e: Messages.send_error_load( 'Connection between {}({}) and {}({}) could not be made.\n\t{}'.format( - source_block_id, source_key, sink_block_id, sink_key, e)) - errors = True + src_blk_id, src_port_id, snk_blk_id, snk_port_id, e)) + had_connect_errors = True self.rewrite() # global rewrite - return errors + return had_connect_errors def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block): @@ -475,27 +426,11 @@ def _update_old_message_port_keys(source_key, sink_key, source_block, sink_block message port. """ try: - # get ports using the "old way" (assuming liner indexed keys) + # get ports using the "old way" (assuming linear indexed keys) source_port = source_block.sources[int(source_key)] sink_port = sink_block.sinks[int(sink_key)] - if source_port.get_type() == "message" and sink_port.get_type() == "message": + if source_port.dtype == "message" and sink_port.dtype == "message": source_key, sink_key = source_port.key, sink_port.key except (ValueError, IndexError): pass return source_key, sink_key # do nothing - - -def _guess_file_format_1(n): - """ - Try to guess the file format for flow-graph files without version tag - """ - try: - has_non_numeric_message_keys = any(not ( - connection_n.get('source_key', '').isdigit() and - connection_n.get('sink_key', '').isdigit() - ) for connection_n in n.get('flow_graph', []).get('connection', [])) - if has_non_numeric_message_keys: - return 1 - except: - pass - return 0 diff --git a/grc/core/Param.py b/grc/core/Param.py index be86f0aecb..e8c81383f3 100644 --- a/grc/core/Param.py +++ b/grc/core/Param.py @@ -20,27 +20,27 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import import ast +import numbers import re import collections +import six from six.moves import builtins, filter, map, range, zip -from . import Constants -from .Element import Element, nop_write +from . import Constants, blocks +from .base import Element +from .utils.descriptors import Evaluated, EvaluatedEnum, setup_names # Blacklist certain ids, its not complete, but should help ID_BLACKLIST = ['self', 'options', 'gr', 'math', 'firdes'] + dir(builtins) try: from gnuradio import gr ID_BLACKLIST.extend(attr for attr in dir(gr.top_block()) if not attr.startswith('_')) -except ImportError: +except (ImportError, AttributeError): pass -_check_id_matcher = re.compile('^[a-z|A-Z]\w*$') -_show_id_matcher = re.compile('^(variable\w*|parameter|options|notebook)$') - -class TemplateArg(object): +class TemplateArg(str): """ A cheetah template argument created from a param. The str of this class evaluates to the param's to code method. @@ -48,123 +48,119 @@ class TemplateArg(object): The __call__ or () method can return the param evaluated to a raw python data type. """ - def __init__(self, param): - self._param = param + def __new__(cls, param): + value = param.to_code() + instance = str.__new__(cls, value) + setattr(instance, '_param', param) + return instance - def __getitem__(self, item): + def __getattr__(self, item): param = self._param - opts = param.options_opts[param.get_value()] - return str(opts[item]) if param.is_enum() else NotImplemented - - def __str__(self): - return str(self._param.to_code()) + attributes = param.options.attributes[param.get_value()] + return str(attributes.get(item)) or NotImplemented def __call__(self): return self._param.get_evaluated() +@setup_names class Param(Element): is_param = True - def __init__(self, parent, key, name, type='raw', value='', **n): + name = Evaluated(str, default='no name') + dtype = EvaluatedEnum(Constants.PARAM_TYPE_NAMES, default='raw') + hide = EvaluatedEnum('none all part') + + # region init + def __init__(self, parent, id, label='', dtype='raw', default='', + options=None, option_labels=None, option_attributes=None, + category='', hide='none', **_): """Make a new param from nested data""" super(Param, self).__init__(parent) - self.key = key - self._name = name - self.value = self.default = value - self._type = type + self.key = id + self.name = label.strip() or id.title() + self.category = category or Constants.DEFAULT_PARAM_TAB - self._hide = n.get('hide', '') - self.tab_label = n.get('tab', Constants.DEFAULT_PARAM_TAB) - self._evaluated = None + self.dtype = dtype + self.value = self.default = str(default) - self.options = [] - self.options_names = [] - self.options_opts = {} - self._init_options(options_n=n.get('option', [])) + self.options = self._init_options(options or [], option_labels or [], + option_attributes or {}) + self.hide = hide or 'none' + # end of args ######################################################## + self._evaluated = None + self._stringify_flag = False + self._lisitify_flag = False self._init = False - self._hostage_cells = list() - self.template_arg = TemplateArg(self) - - def _init_options(self, options_n): - """Create the Option objects from the n data""" - option_keys = set() - for option_n in options_n: - key, name = option_n['key'], option_n['name'] - # Test against repeated keys - if key in option_keys: - raise KeyError('Key "{}" already exists in options'.format(key)) - option_keys.add(key) - # Store the option - self.options.append(key) - self.options_names.append(name) - if self.is_enum(): - self._init_enum(options_n) + @property + def template_arg(self): + return TemplateArg(self) + + def _init_options(self, values, labels, attributes): + """parse option and option attributes""" + options = collections.OrderedDict() + options.attributes = collections.defaultdict(dict) - def _init_enum(self, options_n): - opt_ref = None - for option_n in options_n: - key, opts_raw = option_n['key'], option_n.get('opt', []) + padding = [''] * max(len(values), len(labels)) + attributes = {key: value + padding for key, value in six.iteritems(attributes)} + + for i, option in enumerate(values): + # Test against repeated keys + if option in options: + raise KeyError('Value "{}" already exists in options'.format(option)) + # get label try: - self.options_opts[key] = opts = dict(opt.split(':') for opt in opts_raw) - except TypeError: - raise ValueError('Error separating opts into key:value') - - if opt_ref is None: - opt_ref = set(opts.keys()) - elif opt_ref != set(opts): - raise ValueError('Opt keys ({}) are not identical across all options.' - ''.format(', '.join(opt_ref))) + label = str(labels[i]) + except IndexError: + label = str(option) + # Store the option + options[option] = label + options.attributes[option] = {attrib: values[i] for attrib, values in six.iteritems(attributes)} + + default = next(iter(options)) if options else '' if not self.value: - self.value = self.default = self.options[0] - elif self.value not in self.options: - self.value = self.default = self.options[0] # TODO: warn + self.value = self.default = default + + if self.is_enum() and self.value not in options: + self.value = self.default = default # TODO: warn # raise ValueError('The value {!r} is not in the possible values of {}.' # ''.format(self.get_value(), ', '.join(self.options))) + return options + # endregion def __str__(self): return 'Param - {}({})'.format(self.name, self.key) - def get_hide(self): - """ - Get the hide value from the base class. - Hide the ID parameter for most blocks. Exceptions below. - If the parameter controls a port type, vlen, or nports, return part. - If the parameter is an empty grid position, return part. - These parameters are redundant to display in the flow graph view. + def __repr__(self): + return '{!r}.param[{}]'.format(self.parent, self.key) - Returns: - hide the hide property string - """ - hide = self.parent.resolve_dependencies(self._hide).strip() - if hide: - return hide - # Hide ID in non variable blocks - if self.key == 'id' and not _show_id_matcher.match(self.parent.key): - return 'part' - # Hide port controllers for type and nports - if self.key in ' '.join([' '.join([p._type, p._nports]) for p in self.parent.get_ports()]): - return 'part' - # Hide port controllers for vlen, when == 1 - if self.key in ' '.join(p._vlen for p in self.parent.get_ports()): - try: - if int(self.get_evaluated()) == 1: - return 'part' - except: - pass - return hide + def is_enum(self): + return self.get_raw('dtype') == 'enum' - def validate(self): - """ - Validate the param. - The value must be evaluated and type must a possible type. - """ - Element.validate(self) - if self.get_type() not in Constants.PARAM_TYPE_NAMES: - self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type())) + def get_value(self): + value = self.value + if self.is_enum() and value not in self.options: + value = self.default + self.set_value(value) + return value + + def set_value(self, value): + # Must be a string + self.value = str(value) + + def set_default(self, value): + if self.default == self.value: + self.set_value(value) + self.default = str(value) + + def rewrite(self): + Element.rewrite(self) + del self.name + del self.dtype + del self.hide self._evaluated = None try: @@ -172,6 +168,15 @@ class Param(Element): except Exception as e: self.add_error_message(str(e)) + def validate(self): + """ + Validate the param. + The value must be evaluated and type must a possible type. + """ + Element.validate(self) + if self.dtype not in Constants.PARAM_TYPE_NAMES: + self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype)) + def get_evaluated(self): return self._evaluated @@ -185,150 +190,112 @@ class Param(Element): self._init = True self._lisitify_flag = False self._stringify_flag = False - self._hostage_cells = list() - t = self.get_type() - v = self.get_value() + dtype = self.dtype + expr = self.get_value() ######################### # Enum Type ######################### if self.is_enum(): - return v + return expr ######################### # Numeric Types ######################### - elif t in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'): + elif dtype in ('raw', 'complex', 'real', 'float', 'int', 'hex', 'bool'): # Raise exception if python cannot evaluate this value try: - e = self.parent_flowgraph.evaluate(v) - except Exception as e: - raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e)) + value = self.parent_flowgraph.evaluate(expr) + except Exception as value: + raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value)) # Raise an exception if the data is invalid - if t == 'raw': - return e - elif t == 'complex': - if not isinstance(e, Constants.COMPLEX_TYPES): - raise Exception('Expression "{}" is invalid for type complex.'.format(str(e))) - return e - elif t == 'real' or t == 'float': - if not isinstance(e, Constants.REAL_TYPES): - raise Exception('Expression "{}" is invalid for type float.'.format(str(e))) - return e - elif t == 'int': - if not isinstance(e, Constants.INT_TYPES): - raise Exception('Expression "{}" is invalid for type integer.'.format(str(e))) - return e - elif t == 'hex': - return hex(e) - elif t == 'bool': - if not isinstance(e, bool): - raise Exception('Expression "{}" is invalid for type bool.'.format(str(e))) - return e + if dtype == 'raw': + return value + elif dtype == 'complex': + if not isinstance(value, Constants.COMPLEX_TYPES): + raise Exception('Expression "{}" is invalid for type complex.'.format(str(value))) + return value + elif dtype in ('real', 'float'): + if not isinstance(value, Constants.REAL_TYPES): + raise Exception('Expression "{}" is invalid for type float.'.format(str(value))) + return value + elif dtype == 'int': + if not isinstance(value, Constants.INT_TYPES): + raise Exception('Expression "{}" is invalid for type integer.'.format(str(value))) + return value + elif dtype == 'hex': + return hex(value) + elif dtype == 'bool': + if not isinstance(value, bool): + raise Exception('Expression "{}" is invalid for type bool.'.format(str(value))) + return value else: - raise TypeError('Type "{}" not handled'.format(t)) + raise TypeError('Type "{}" not handled'.format(dtype)) ######################### # Numeric Vector Types ######################### - elif t in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): - if not v: - # Turn a blank string into an empty list, so it will eval - v = '()' - # Raise exception if python cannot evaluate this value + elif dtype in ('complex_vector', 'real_vector', 'float_vector', 'int_vector'): + default = [] + + if not expr: + return default # Turn a blank string into an empty list, so it will eval + try: - e = self.parent.parent.evaluate(v) - except Exception as e: - raise Exception('Value "{}" cannot be evaluated:\n{}'.format(v, e)) + value = self.parent.parent.evaluate(expr) + except Exception as value: + raise Exception('Value "{}" cannot be evaluated:\n{}'.format(expr, value)) + + if not isinstance(value, Constants.VECTOR_TYPES): + self._lisitify_flag = True + value = [value] + # Raise an exception if the data is invalid - if t == 'complex_vector': - if not isinstance(e, Constants.VECTOR_TYPES): - self._lisitify_flag = True - e = [e] - if not all([isinstance(ei, Constants.COMPLEX_TYPES) for ei in e]): - raise Exception('Expression "{}" is invalid for type complex vector.'.format(str(e))) - return e - elif t == 'real_vector' or t == 'float_vector': - if not isinstance(e, Constants.VECTOR_TYPES): - self._lisitify_flag = True - e = [e] - if not all([isinstance(ei, Constants.REAL_TYPES) for ei in e]): - raise Exception('Expression "{}" is invalid for type float vector.'.format(str(e))) - return e - elif t == 'int_vector': - if not isinstance(e, Constants.VECTOR_TYPES): - self._lisitify_flag = True - e = [e] - if not all([isinstance(ei, Constants.INT_TYPES) for ei in e]): - raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(e))) - return e + if dtype == 'complex_vector' and not all(isinstance(item, numbers.Complex) for item in value): + raise Exception('Expression "{}" is invalid for type complex vector.'.format(value)) + elif dtype in ('real_vector', 'float_vector') and not all(isinstance(item, numbers.Real) for item in value): + raise Exception('Expression "{}" is invalid for type float vector.'.format(value)) + elif dtype == 'int_vector' and not all(isinstance(item, Constants.INT_TYPES) for item in value): + raise Exception('Expression "{}" is invalid for type integer vector.'.format(str(value))) + return value ######################### # String Types ######################### - elif t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): + 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: - e = self.parent.parent.evaluate(v) - if not isinstance(e, str): + value = self.parent.parent.evaluate(expr) + if not isinstance(value, str): raise Exception() except: self._stringify_flag = True - e = str(v) - if t == '_multiline_python_external': - ast.parse(e) # Raises SyntaxError - return e + value = str(expr) + if dtype == '_multiline_python_external': + ast.parse(value) # Raises SyntaxError + return value ######################### # Unique ID Type ######################### - elif t == 'id': - # Can python use this as a variable? - if not _check_id_matcher.match(v): - raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, and underscores.'.format(v)) - ids = [param.get_value() for param in self.get_all_params(t, 'id')] - - if v in ID_BLACKLIST: - raise Exception('ID "{}" is blacklisted.'.format(v)) - - if self.key == 'id': - # Id should only appear once, or zero times if block is disabled - if ids.count(v) > 1: - raise Exception('ID "{}" is not unique.'.format(v)) - else: - # Id should exist to be a reference - if ids.count(v) < 1: - raise Exception('ID "{}" does not exist.'.format(v)) - - return v + elif dtype == 'id': + self.validate_block_id() + return expr ######################### # Stream ID Type ######################### - elif t == 'stream_id': - # Get a list of all stream ids used in the virtual sinks - ids = [param.get_value() for param in filter( - lambda p: p.parent.is_virtual_sink(), - self.get_all_params(t), - )] - # Check that the virtual sink's stream id is unique - if self.parent.is_virtual_sink(): - # Id should only appear once, or zero times if block is disabled - if ids.count(v) > 1: - raise Exception('Stream ID "{}" is not unique.'.format(v)) - # Check that the virtual source's steam id is found - if self.parent.is_virtual_source(): - if v not in ids: - raise Exception('Stream ID "{}" is not found.'.format(v)) - return v + elif dtype == 'stream_id': + self.validate_stream_id() + return expr ######################### # GUI Position/Hint ######################### - elif t == 'gui_hint': - if ':' in v: - tab, pos = v.split(':') - elif '@' in v: - tab, pos = v, '' + elif dtype == 'gui_hint': + if ':' in expr: + tab, pos = expr.split(':') + elif '@' in expr: + tab, pos = expr, '' else: - tab, pos = '', v + tab, pos = '', expr if '@' in tab: tab, index = tab.split('@') @@ -358,20 +325,51 @@ class Param(Element): ######################### # Import Type ######################### - elif t == 'import': + elif dtype == 'import': # New namespace n = dict() try: - exec(v, n) + exec(expr, n) except ImportError: - raise Exception('Import "{}" failed.'.format(v)) + raise Exception('Import "{}" failed.'.format(expr)) except Exception: - raise Exception('Bad import syntax: "{}".'.format(v)) + raise Exception('Bad import syntax: "{}".'.format(expr)) return [k for k in list(n.keys()) if str(k) != '__builtins__'] ######################### else: - raise TypeError('Type "{}" not handled'.format(t)) + raise TypeError('Type "{}" not handled'.format(dtype)) + + def validate_block_id(self): + value = self.value + # Can python use this as a variable? + if not re.match(r'^[a-z|A-Z]\w*$', value): + raise Exception('ID "{}" must begin with a letter and may contain letters, numbers, ' + 'and underscores.'.format(value)) + if value in ID_BLACKLIST: + raise Exception('ID "{}" is blacklisted.'.format(value)) + block_names = [block.name for block in self.parent_flowgraph.iter_enabled_blocks()] + # Id should only appear once, or zero times if block is disabled + if self.key == 'id' and block_names.count(value) > 1: + raise Exception('ID "{}" is not unique.'.format(value)) + elif value not in block_names: + raise Exception('ID "{}" does not exist.'.format(value)) + return value + + def validate_stream_id(self): + value = self.value + stream_ids = [ + block.params['stream_id'].value + for block in self.parent_flowgraph.iter_enabled_blocks() + if isinstance(block, blocks.VirtualSink) + ] + # Check that the virtual sink's stream id is unique + if isinstance(self.parent_block, blocks.VirtualSink) and stream_ids.count(value) >= 2: + # Id should only appear once, or zero times if block is disabled + raise Exception('Stream ID "{}" is not unique.'.format(value)) + # Check that the virtual source's steam id is found + elif isinstance(self.parent_block, blocks.VirtualSource) and value not in stream_ids: + raise Exception('Stream ID "{}" is not found.'.format(value)) def to_code(self): """ @@ -382,8 +380,9 @@ class Param(Element): Returns: a string representing the code """ + self._init = True v = self.get_value() - t = self.get_type() + t = self.dtype # String types if t in ('string', 'file_open', 'file_save', '_multiline', '_multiline_python_external'): if not self._init: @@ -400,71 +399,3 @@ class Param(Element): return '(%s)' % v else: return v - - def get_all_params(self, type, key=None): - """ - Get all the params from the flowgraph that have the given type and - optionally a given key - - Args: - type: the specified type - key: the key to match against - - Returns: - a list of params - """ - params = [] - for block in self.parent_flowgraph.get_enabled_blocks(): - params.extend(p for k, p in block.params.items() if p.get_type() == type and (key is None or key == k)) - return params - - def is_enum(self): - return self._type == 'enum' - - def get_value(self): - value = self.value - if self.is_enum() and value not in self.options: - value = self.options[0] - self.set_value(value) - return value - - def set_value(self, value): - # Must be a string - self.value = str(value) - - def set_default(self, value): - if self.default == self.value: - self.set_value(value) - self.default = str(value) - - def get_type(self): - return self.parent.resolve_dependencies(self._type) - - def get_tab_label(self): - return self.tab_label - - @nop_write - @property - def name(self): - return self.parent.resolve_dependencies(self._name).strip() - - ############################################## - # Access Options - ############################################## - def opt_value(self, key): - return self.options_opts[self.get_value()][key] - - ############################################## - # Import/Export Methods - ############################################## - def export_data(self): - """ - Export this param's key/value. - - Returns: - a nested data odict - """ - n = collections.OrderedDict() - n['key'] = self.key - n['value'] = self.get_value() - return n diff --git a/grc/core/Platform.py b/grc/core/Platform.py deleted file mode 100644 index 73937f1299..0000000000 --- a/grc/core/Platform.py +++ /dev/null @@ -1,341 +0,0 @@ -""" -Copyright 2008-2016 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 __future__ import absolute_import, print_function - -import os -import sys - -import six -from six.moves import range - -from . import ParseXML, Messages, Constants - -from .Config import Config -from .Element import Element -from .generator import Generator -from .FlowGraph import FlowGraph -from .Connection import Connection -from . import Block -from .Port import Port, PortClone -from .Param import Param - -from .utils import extract_docs - - -class Platform(Element): - - is_platform = True - - def __init__(self, *args, **kwargs): - """ Make a platform for GNU Radio """ - Element.__init__(self, parent=None) - - self.config = self.Config(*args, **kwargs) - self.block_docstrings = {} - self.block_docstrings_loaded_callback = lambda: None # dummy to be replaced by BlockTreeWindow - - self._docstring_extractor = extract_docs.SubprocessLoader( - callback_query_result=self._save_docstring_extraction_result, - callback_finished=lambda: self.block_docstrings_loaded_callback() - ) - - self.blocks = {} - self._blocks_n = {} - self._block_categories = {} - self.domains = {} - self.connection_templates = {} - - self._auto_hier_block_generate_chain = set() - - # Create a dummy flow graph for the blocks - self._flow_graph = Element.__new__(FlowGraph) - Element.__init__(self._flow_graph, self) - self._flow_graph.connections = [] - - self.build_block_library() - - def __str__(self): - return 'Platform - {}'.format(self.config.name) - - @staticmethod - def find_file_in_paths(filename, paths, cwd): - """Checks the provided paths relative to cwd for a certain filename""" - if not os.path.isdir(cwd): - cwd = os.path.dirname(cwd) - if isinstance(paths, str): - paths = (p for p in paths.split(':') if p) - - for path in paths: - path = os.path.expanduser(path) - if not os.path.isabs(path): - path = os.path.normpath(os.path.join(cwd, path)) - file_path = os.path.join(path, filename) - if os.path.exists(os.path.normpath(file_path)): - return file_path - - def load_and_generate_flow_graph(self, file_path, out_path=None, hier_only=False): - """Loads a flow graph from file and generates it""" - Messages.set_indent(len(self._auto_hier_block_generate_chain)) - Messages.send('>>> Loading: {}\n'.format(file_path)) - if file_path in self._auto_hier_block_generate_chain: - Messages.send(' >>> Warning: cyclic hier_block dependency\n') - return None, None - self._auto_hier_block_generate_chain.add(file_path) - try: - flow_graph = self.get_new_flow_graph() - flow_graph.grc_file_path = file_path - # Other, nested hier_blocks might be auto-loaded here - flow_graph.import_data(self.parse_flow_graph(file_path)) - flow_graph.rewrite() - flow_graph.validate() - if not flow_graph.is_valid(): - raise Exception('Flowgraph invalid') - if hier_only and not flow_graph.get_option('generate_options').startswith('hb'): - raise Exception('Not a hier block') - except Exception as e: - Messages.send('>>> Load Error: {}: {}\n'.format(file_path, str(e))) - return None, None - finally: - self._auto_hier_block_generate_chain.discard(file_path) - Messages.set_indent(len(self._auto_hier_block_generate_chain)) - - try: - generator = self.Generator(flow_graph, out_path or file_path) - Messages.send('>>> Generating: {}\n'.format(generator.file_path)) - generator.write() - except Exception as e: - Messages.send('>>> Generate Error: {}: {}\n'.format(file_path, str(e))) - return None, None - - if flow_graph.get_option('generate_options').startswith('hb'): - self.load_block_xml(generator.file_path_xml) - return flow_graph, generator.file_path - - def build_block_library(self): - """load the blocks and block tree from the search paths""" - self._docstring_extractor.start() - - # Reset - self.blocks.clear() - self._blocks_n.clear() - self._block_categories.clear() - self.domains.clear() - self.connection_templates.clear() - ParseXML.xml_failures.clear() - - # Try to parse and load blocks - for xml_file in self.iter_xml_files(): - try: - if xml_file.endswith("block_tree.xml"): - self.load_category_tree_xml(xml_file) - elif xml_file.endswith('domain.xml'): - self.load_domain_xml(xml_file) - else: - self.load_block_xml(xml_file) - except ParseXML.XMLSyntaxError as e: - # print >> sys.stderr, 'Warning: Block validation failed:\n\t%s\n\tIgnoring: %s' % (e, xml_file) - pass - except Exception as e: - raise - print('Warning: XML parsing failed:\n\t%r\n\tIgnoring: %s' % (e, xml_file), file=sys.stderr) - - # Add blocks to block tree - for key, block in six.iteritems(self.blocks): - category = self._block_categories.get(key, block.category) - # Blocks with empty categories are hidden - if not category: - continue - root = category[0] - if root.startswith('[') and root.endswith(']'): - category[0] = root[1:-1] - else: - category.insert(0, Constants.DEFAULT_BLOCK_MODULE_NAME) - block.category = category - - self._docstring_extractor.finish() - # self._docstring_extractor.wait() - - def iter_xml_files(self): - """Iterator for block descriptions and category trees""" - for block_path in self.config.block_paths: - if os.path.isfile(block_path): - yield block_path - elif os.path.isdir(block_path): - for dirpath, dirnames, filenames in os.walk(block_path): - for filename in sorted(f for f in filenames if f.endswith('.xml')): - yield os.path.join(dirpath, filename) - - def load_block_xml(self, xml_file): - """Load block description from xml file""" - # Validate and import - ParseXML.validate_dtd(xml_file, Constants.BLOCK_DTD) - n = ParseXML.from_file(xml_file).get('block', {}) - n['block_wrapper_path'] = xml_file # inject block wrapper path - key = n.pop('key') - - if key in self.blocks: - print('Warning: Block with key "{}" already exists.\n' - '\tIgnoring: {}'.format(key, xml_file), file=sys.stderr) - return - - # Store the block - self.blocks[key] = block = self.get_new_block(self._flow_graph, key, **n) - self._blocks_n[key] = n - self._docstring_extractor.query( - key, - block.get_imports(raw=True), - block.get_make(raw=True) - ) - - def load_category_tree_xml(self, xml_file): - """Validate and parse category tree file and add it to list""" - ParseXML.validate_dtd(xml_file, Constants.BLOCK_TREE_DTD) - xml = ParseXML.from_file(xml_file) - path = [] - - def load_category(cat_n): - path.append(cat_n.get('name').strip()) - for block_key in cat_n.get('block', []): - if block_key not in self._block_categories: - self._block_categories[block_key] = list(path) - for sub_cat_n in cat_n.get('cat', []): - load_category(sub_cat_n) - path.pop() - - load_category(xml.get('cat', {})) - - def load_domain_xml(self, xml_file): - """Load a domain properties and connection templates from XML""" - ParseXML.validate_dtd(xml_file, Constants.DOMAIN_DTD) - n = ParseXML.from_file(xml_file).get('domain') - - key = n.get('key') - if not key: - print('Warning: Domain with emtpy key.\n\tIgnoring: {}'.format(xml_file), file=sys.stderr) - return - if key in self.domains: # test against repeated keys - print('Warning: Domain with key "{}" already exists.\n\tIgnoring: {}'.format(key, xml_file), file=sys.stderr) - return - - # to_bool = lambda s, d: d if s is None else s.lower() not in ('false', 'off', '0', '') - def to_bool(s, d): - if s is not None: - return s.lower() not in ('false', 'off', '0', '') - return d - - color = n.get('color') or '' - try: - chars_per_color = 2 if len(color) > 4 else 1 - tuple(int(color[o:o + 2], 16) / 255.0 for o in range(1, 3 * chars_per_color, chars_per_color)) - except ValueError: - if color: # no color is okay, default set in GUI - print('Warning: Can\'t parse color code "{}" for domain "{}" '.format(color, key), file=sys.stderr) - color = None - - self.domains[key] = dict( - name=n.get('name') or key, - multiple_sinks=to_bool(n.get('multiple_sinks'), True), - multiple_sources=to_bool(n.get('multiple_sources'), False), - color=color - ) - for connection_n in n.get('connection', []): - key = (connection_n.get('source_domain'), connection_n.get('sink_domain')) - if not all(key): - print('Warning: Empty domain key(s) in connection template.\n\t{}'.format(xml_file), file=sys.stderr) - elif key in self.connection_templates: - print('Warning: Connection template "{}" already exists.\n\t{}'.format(key, xml_file), file=sys.stderr) - else: - self.connection_templates[key] = connection_n.get('make') or '' - - def _save_docstring_extraction_result(self, key, docstrings): - docs = {} - for match, docstring in six.iteritems(docstrings): - if not docstring or match.endswith('_sptr'): - continue - docstring = docstring.replace('\n\n', '\n').strip() - docs[match] = docstring - self.block_docstrings[key] = docs - - ############################################## - # Access - ############################################## - - def parse_flow_graph(self, flow_graph_file): - """ - Parse a saved flow graph file. - Ensure that the file exists, and passes the dtd check. - - Args: - flow_graph_file: the flow graph file - - Returns: - nested data - @throws exception if the validation fails - """ - flow_graph_file = flow_graph_file or self.config.default_flow_graph - open(flow_graph_file, 'r').close() # Test open - ParseXML.validate_dtd(flow_graph_file, Constants.FLOW_GRAPH_DTD) - return ParseXML.from_file(flow_graph_file) - - def get_blocks(self): - return list(self.blocks.values()) - - def get_generate_options(self): - gen_opts = self.blocks['options'].get_param('generate_options') - generate_mode_default = gen_opts.get_value() - return [(key, name, key == generate_mode_default) - for key, name in zip(gen_opts.options, gen_opts.options_names)] - - ############################################## - # Factories - ############################################## - Config = Config - Generator = Generator - FlowGraph = FlowGraph - Connection = Connection - block_classes = { - None: Block.Block, # default - 'epy_block': Block.EPyBlock, - '_dummy': Block.DummyBlock, - } - port_classes = { - None: Port, # default - 'clone': PortClone, # default - } - param_classes = { - None: Param, # default - } - - def get_new_flow_graph(self): - return self.FlowGraph(parent=self) - - def get_new_block(self, parent, key, **kwargs): - cls = self.block_classes.get(key, self.block_classes[None]) - if not kwargs: - kwargs = self._blocks_n[key] - return cls(parent, key=key, **kwargs) - - def get_new_param(self, parent, **kwargs): - cls = self.param_classes[kwargs.pop('cls_key', None)] - return cls(parent, **kwargs) - - def get_new_port(self, parent, **kwargs): - cls = self.port_classes[kwargs.pop('cls_key', None)] - return cls(parent, **kwargs) diff --git a/grc/core/Port.py b/grc/core/Port.py deleted file mode 100644 index 9ca443efa1..0000000000 --- a/grc/core/Port.py +++ /dev/null @@ -1,391 +0,0 @@ -""" -Copyright 2008-2017 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 __future__ import absolute_import - -from itertools import chain - -from six.moves import filter - -from .Element import Element, lazy_property -from . import Constants - - -class LoopError(Exception): - pass - - -def _upstream_ports(port): - if port.is_sink: - return _sources_from_virtual_sink_port(port) - else: - return _sources_from_virtual_source_port(port) - - -def _sources_from_virtual_sink_port(sink_port, _traversed=None): - """ - Resolve the source port that is connected to the given virtual sink port. - Use the get source from virtual source to recursively resolve subsequent ports. - """ - source_ports_per_virtual_connection = ( - # there can be multiple ports per virtual connection - _sources_from_virtual_source_port(c.source_port, _traversed) # type: list - for c in sink_port.get_enabled_connections() - ) - return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports - - -def _sources_from_virtual_source_port(source_port, _traversed=None): - """ - Recursively resolve source ports over the virtual connections. - Keep track of traversed sources to avoid recursive loops. - """ - _traversed = set(_traversed or []) # a new set! - if source_port in _traversed: - raise LoopError('Loop found when resolving port type') - _traversed.add(source_port) - - block = source_port.parent_block - flow_graph = source_port.parent_flow_graph - - if not block.is_virtual_source(): - return [source_port] # nothing to resolve, we're done - - stream_id = block.get_param('stream_id').get_value() - - # currently the validation does not allow multiple virtual sinks and one virtual source - # but in the future it may... - connected_virtual_sink_blocks = ( - b for b in flow_graph.iter_enabled_blocks() - if b.is_virtual_sink() and b.get_param('stream_id').get_value() == stream_id - ) - source_ports_per_virtual_connection = ( - _sources_from_virtual_sink_port(b.sinks[0], _traversed) # type: list - for b in connected_virtual_sink_blocks - ) - return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports - - -def _downstream_ports(port): - if port.is_source: - return _sinks_from_virtual_source_port(port) - else: - return _sinks_from_virtual_sink_port(port) - - -def _sinks_from_virtual_source_port(source_port, _traversed=None): - """ - Resolve the sink port that is connected to the given virtual source port. - Use the get sink from virtual sink to recursively resolve subsequent ports. - """ - sink_ports_per_virtual_connection = ( - # there can be multiple ports per virtual connection - _sinks_from_virtual_sink_port(c.sink_port, _traversed) # type: list - for c in source_port.get_enabled_connections() - ) - return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports - - -def _sinks_from_virtual_sink_port(sink_port, _traversed=None): - """ - Recursively resolve sink ports over the virtual connections. - Keep track of traversed sinks to avoid recursive loops. - """ - _traversed = set(_traversed or []) # a new set! - if sink_port in _traversed: - raise LoopError('Loop found when resolving port type') - _traversed.add(sink_port) - - block = sink_port.parent_block - flow_graph = sink_port.parent_flow_graph - - if not block.is_virtual_sink(): - return [sink_port] - - stream_id = block.get_param('stream_id').get_value() - - connected_virtual_source_blocks = ( - b for b in flow_graph.iter_enabled_blocks() - if b.is_virtual_source() and b.get_param('stream_id').get_value() == stream_id - ) - sink_ports_per_virtual_connection = ( - _sinks_from_virtual_source_port(b.sources[0], _traversed) # type: list - for b in connected_virtual_source_blocks - ) - return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports - - -class Port(Element): - - is_port = True - is_clone = False - - def __init__(self, parent, direction, **n): - """ - Make a new port from nested data. - - Args: - block: the parent element - n: the nested odict - dir: the direction - """ - self._n = n - if n['type'] == 'message': - n['domain'] = Constants.GR_MESSAGE_DOMAIN - - if 'domain' not in n: - n['domain'] = Constants.DEFAULT_DOMAIN - elif n['domain'] == Constants.GR_MESSAGE_DOMAIN: - n['key'] = n['name'] - n['type'] = 'message' # For port color - - # Build the port - Element.__init__(self, parent) - # Grab the data - self.name = n['name'] - self.key = n['key'] - self.domain = n.get('domain') - self._type = n.get('type', '') - self.inherit_type = not self._type - self._hide = n.get('hide', '') - self._dir = direction - self._hide_evaluated = False # Updated on rewrite() - - self._nports = n.get('nports', '') - self._vlen = n.get('vlen', '') - self._optional = bool(n.get('optional')) - self._optional_evaluated = False # Updated on rewrite() - self.clones = [] # References to cloned ports (for nports > 1) - - def __str__(self): - if self.is_source: - return 'Source - {}({})'.format(self.name, self.key) - if self.is_sink: - return 'Sink - {}({})'.format(self.name, self.key) - - def validate(self): - Element.validate(self) - if self.get_type() not in Constants.TYPE_TO_SIZEOF.keys(): - self.add_error_message('Type "{}" is not a possible type.'.format(self.get_type())) - if self.domain not in self.parent_platform.domains: - self.add_error_message('Domain key "{}" is not registered.'.format(self.domain)) - if not self.get_enabled_connections() and not self.get_optional(): - self.add_error_message('Port is not connected.') - - def rewrite(self): - """ - Handle the port cloning for virtual blocks. - """ - del self._error_messages[:] - if self.inherit_type: - self.resolve_empty_type() - - hide = self.parent_block.resolve_dependencies(self._hide).strip().lower() - self._hide_evaluated = False if hide in ('false', 'off', '0') else bool(hide) - optional = self.parent_block.resolve_dependencies(self._optional).strip().lower() - self._optional_evaluated = False if optional in ('false', 'off', '0') else bool(optional) - - # Update domain if was deduced from (dynamic) port type - type_ = self.get_type() - if self.domain == Constants.GR_STREAM_DOMAIN and type_ == "message": - self.domain = Constants.GR_MESSAGE_DOMAIN - self.key = self.name - if self.domain == Constants.GR_MESSAGE_DOMAIN and type_ != "message": - self.domain = Constants.GR_STREAM_DOMAIN - self.key = '0' # Is rectified in rewrite() - - def resolve_virtual_source(self): - """Only used by Generator after validation is passed""" - return _upstream_ports(self) - - def resolve_empty_type(self): - def find_port(finder): - try: - return next((p for p in finder(self) if not p.inherit_type), None) - except LoopError as error: - self.add_error_message(str(error)) - except (StopIteration, Exception) as error: - pass - - try: - port = find_port(_upstream_ports) or find_port(_downstream_ports) - self._type = str(port.get_type()) - self._vlen = str(port.get_vlen()) - except Exception: - # Reset type and vlen - self._type = self._vlen = '' - - def get_vlen(self): - """ - Get the vector length. - If the evaluation of vlen cannot be cast to an integer, return 1. - - Returns: - the vector length or 1 - """ - vlen = self.parent_block.resolve_dependencies(self._vlen) - try: - return max(1, int(self.parent_flowgraph.evaluate(vlen))) - except: - return 1 - - def get_nports(self): - """ - Get the number of ports. - If already blank, return a blank - If the evaluation of nports cannot be cast to a positive integer, return 1. - - Returns: - the number of ports or 1 - """ - if self._nports == '': - return 1 - - nports = self.parent_block.resolve_dependencies(self._nports) - try: - return max(1, int(self.parent_flowgraph.evaluate(nports))) - except: - return 1 - - def get_optional(self): - return self._optional_evaluated - - def add_clone(self): - """ - Create a clone of this (master) port and store a reference in self._clones. - - The new port name (and key for message ports) will have index 1... appended. - If this is the first clone, this (master) port will get a 0 appended to its name (and key) - - Returns: - the cloned port - """ - # Add index to master port name if there are no clones yet - if not self.clones: - self.name = self._n['name'] + '0' - # Also update key for none stream ports - if not self.key.isdigit(): - self.key = self.name - - name = self._n['name'] + str(len(self.clones) + 1) - # Dummy value 99999 will be fixed later - key = '99999' if self.key.isdigit() else name - - # Clone - port_factory = self.parent_platform.get_new_port - port = port_factory(self.parent, direction=self._dir, - name=name, key=key, - master=self, cls_key='clone') - - self.clones.append(port) - return port - - def remove_clone(self, port): - """ - Remove a cloned port (from the list of clones only) - Remove the index 0 of the master port name (and key9 if there are no more clones left - """ - self.clones.remove(port) - # Remove index from master port name if there are no more clones - if not self.clones: - self.name = self._n['name'] - # Also update key for none stream ports - if not self.key.isdigit(): - self.key = self.name - - @lazy_property - def is_sink(self): - return self._dir == 'sink' - - @lazy_property - def is_source(self): - return self._dir == 'source' - - def get_type(self): - return self.parent_block.resolve_dependencies(self._type) - - def get_hide(self): - return self._hide_evaluated - - def get_connections(self): - """ - Get all connections that use this port. - - Returns: - a list of connection objects - """ - connections = self.parent_flowgraph.connections - connections = [c for c in connections if c.source_port is self or c.sink_port is self] - return connections - - def get_enabled_connections(self): - """ - Get all enabled connections that use this port. - - Returns: - a list of connection objects - """ - return [c for c in self.get_connections() if c.enabled] - - def get_associated_ports(self): - if not self.get_type() == 'bus': - return [self] - - block = self.parent_block - if self.is_source: - block_ports = block.sources - bus_structure = block.current_bus_structure['source'] - else: - block_ports = block.sinks - bus_structure = block.current_bus_structure['sink'] - - ports = [i for i in block_ports if not i.get_type() == 'bus'] - if bus_structure: - bus_index = [i for i in block_ports if i.get_type() == 'bus'].index(self) - ports = [p for i, p in enumerate(ports) if i in bus_structure[bus_index]] - return ports - - -class PortClone(Port): - - is_clone = True - - def __init__(self, parent, direction, master, name, key): - """ - Make a new port from nested data. - - Args: - block: the parent element - n: the nested odict - dir: the direction - """ - Element.__init__(self, parent) - self.master = master - self.name = name - self._key = key - self._nports = '1' - - def __getattr__(self, item): - return getattr(self.master, item) - - def add_clone(self): - raise NotImplementedError() - - def remove_clone(self, port): - raise NotImplementedError() diff --git a/grc/core/Element.py b/grc/core/base.py index 86e0746655..e5ff657d85 100644 --- a/grc/core/Element.py +++ b/grc/core/base.py @@ -16,28 +16,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA import weakref -import functools - -class lazy_property(object): - - def __init__(self, func): - self.func = func - functools.update_wrapper(self, func) - - def __get__(self, instance, cls): - if instance is None: - return self - value = self.func(instance) - setattr(instance, self.func.__name__, value) - return value - - -def nop_write(prop): - """Make this a property with a nop setter""" - def nop(self, value): - pass - return prop.setter(nop) +from .utils.descriptors import lazy_property class Element(object): @@ -56,7 +36,7 @@ class Element(object): """ del self._error_messages[:] - for child in self.get_children(): + for child in self.children(): child.validate() def is_valid(self): @@ -97,7 +77,7 @@ class Element(object): """ for msg in self._error_messages: yield self, msg - for child in self.get_children(): + for child in self.children(): if not child.enabled or child.get_bypassed(): continue for element_msg in child.iter_error_messages(): @@ -108,7 +88,7 @@ class Element(object): Rewrite this element and call rewrite on all children. Call this base method before rewriting the element. """ - for child in self.get_children(): + for child in self.children(): child.rewrite() @property @@ -136,7 +116,7 @@ class Element(object): @lazy_property def parent_platform(self): - from .Platform import Platform + from .platform import Platform return self.get_parent_by_type(Platform) @lazy_property @@ -146,7 +126,7 @@ class Element(object): @lazy_property def parent_block(self): - from .Block import Block + from .blocks import Block return self.get_parent_by_type(Block) def reset_parents_by_type(self): @@ -155,26 +135,30 @@ class Element(object): if isinstance(obj, lazy_property): delattr(self, name) - def get_children(self): - return list() + def children(self): + return + yield # empty generator ############################################## # Type testing ############################################## - is_platform = False - is_flow_graph = False - is_block = False - is_dummy_block = False - is_connection = False - is_port = False - is_param = False - is_variable = False - is_import = False + + def get_raw(self, name): + descriptor = getattr(self.__class__, name, None) + if not descriptor: + raise ValueError("No evaluated property '{}' found".format(name)) + return getattr(self, descriptor.name_raw, None) or getattr(self, descriptor.name, None) + + def set_evaluated(self, name, value): + descriptor = getattr(self.__class__, name, None) + if not descriptor: + raise ValueError("No evaluated property '{}' found".format(name)) + self.__dict__[descriptor.name] = value diff --git a/grc/core/Element.pyi b/grc/core/blocks/__init__.py index 2a2aed401c..e4a085d477 100644 --- a/grc/core/Element.pyi +++ b/grc/core/blocks/__init__.py @@ -15,27 +15,23 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA -from typing import Union +from __future__ import absolute_import -from . import Platform, FlowGraph, Block +from ._flags import Flags +from ._templates import MakoTemplates -lazy_property = property # fixme: descriptors don't seems to be supported +from .block import Block +from ._build import build -class Element(object): - def __init__(self, parent: Union[None, 'Element'] = None): ... +build_ins = {} - @lazy_property - def parent(self) -> 'Element': ... - def get_parent_by_type(self, cls) -> Union[None, 'Element']: ... +def register_build_in(cls): + build_ins[cls.key] = cls + return cls - @lazy_property - def parent_platform(self) -> Platform.Platform: ... - - @lazy_property - def parent_flowgraph(self) -> FlowGraph.FlowGraph: ... - - @lazy_property - def parent_block(self) -> Block.Block: ... +from .dummy import DummyBlock +from .embedded_python import EPyBlock, EPyModule +from .virtual import VirtualSink, VirtualSource diff --git a/grc/core/blocks/_build.py b/grc/core/blocks/_build.py new file mode 100644 index 0000000000..9a50086cea --- /dev/null +++ b/grc/core/blocks/_build.py @@ -0,0 +1,69 @@ +# Copyright 2016 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 __future__ import absolute_import + +import re + +from .block import Block +from ._flags import Flags +from ._templates import MakoTemplates + + +def build(id, label='', category='', flags='', documentation='', + checks=None, value=None, + parameters=None, inputs=None, outputs=None, templates=None, **kwargs): + block_id = id + + cls = type(block_id, (Block,), {}) + cls.key = block_id + + cls.label = label or block_id.title() + cls.category = [cat.strip() for cat in category.split('/') if cat.strip()] + + cls.flags = Flags(flags) + if re.match(r'options$|variable|virtual', block_id): + cls.flags += Flags.NOT_DSP + Flags.DISABLE_BYPASS + + cls.documentation = {'': documentation.strip('\n\t ').replace('\\\n', '')} + + cls.checks = [_single_mako_expr(check, block_id) for check in (checks or [])] + + cls.parameters_data = parameters or [] + cls.inputs_data = inputs or [] + cls.outputs_data = outputs or [] + cls.extra_data = kwargs + + templates = templates or {} + cls.templates = MakoTemplates( + imports=templates.get('imports', ''), + make=templates.get('make', ''), + callbacks=templates.get('callbacks', []), + var_make=templates.get('var_make', ''), + ) + # todo: MakoTemplates.compile() to check for errors + + cls.value = _single_mako_expr(value, block_id) + + return cls + + +def _single_mako_expr(value, block_id): + match = re.match(r'\s*\$\{\s*(.*?)\s*\}\s*', str(value)) + if value and not match: + raise ValueError('{} is not a mako substitution in {}'.format(value, block_id)) + return match.group(1) if match else None diff --git a/grc/core/blocks/_flags.py b/grc/core/blocks/_flags.py new file mode 100644 index 0000000000..ffea2ad569 --- /dev/null +++ b/grc/core/blocks/_flags.py @@ -0,0 +1,39 @@ +# Copyright 2016 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 __future__ import absolute_import + +import six + + +class Flags(six.text_type): + + THROTTLE = 'throttle' + DISABLE_BYPASS = 'disable_bypass' + NEED_QT_GUI = 'need_qt_gui' + DEPRECATED = 'deprecated' + NOT_DSP = 'not_dsp' + + def __getattr__(self, item): + return item in self + + def __add__(self, other): + if not isinstance(other, six.string_types): + return NotImplemented + return self.__class__(str(self) + other) + + __iadd__ = __add__ diff --git a/grc/core/blocks/_templates.py b/grc/core/blocks/_templates.py new file mode 100644 index 0000000000..0b15166423 --- /dev/null +++ b/grc/core/blocks/_templates.py @@ -0,0 +1,77 @@ +# Copyright 2016 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 +""" +This dict class holds a (shared) cache of compiled mako templates. +These + +""" +from __future__ import absolute_import, print_function + +from mako.template import Template +from mako.exceptions import SyntaxException + +from ..errors import TemplateError + + +class MakoTemplates(dict): + + _template_cache = {} + + def __init__(self, _bind_to=None, *args, **kwargs): + self.instance = _bind_to + dict.__init__(self, *args, **kwargs) + + def __get__(self, instance, owner): + if instance is None or self.instance is not None: + return self + copy = self.__class__(_bind_to=instance, **self) + if getattr(instance.__class__, 'templates', None) is self: + setattr(instance, 'templates', copy) + return copy + + @classmethod + def compile(cls, text): + text = str(text) + try: + template = Template(text) + except SyntaxException as error: + raise TemplateError(text, *error.args) + + cls._template_cache[text] = template + return template + + def _get_template(self, text): + try: + return self._template_cache[str(text)] + except KeyError: + return self.compile(text) + + def render(self, item): + text = self.get(item) + if not text: + return '' + namespace = self.instance.namespace_templates + + try: + if isinstance(text, list): + templates = (self._get_template(t) for t in text) + return [template.render(**namespace) for template in templates] + else: + template = self._get_template(text) + return template.render(**namespace) + except Exception as error: + raise TemplateError(error, text) diff --git a/grc/core/blocks/block.py b/grc/core/blocks/block.py new file mode 100644 index 0000000000..e6695083a1 --- /dev/null +++ b/grc/core/blocks/block.py @@ -0,0 +1,416 @@ +""" +Copyright 2008-2015 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 __future__ import absolute_import + +import ast +import collections +import itertools + +import six +from six.moves import range + +from ._templates import MakoTemplates +from ._flags import Flags + +from ..Constants import ADVANCED_PARAM_TAB +from ..base import Element +from ..utils.descriptors import lazy_property + + +def _get_elem(iterable, key): + items = list(iterable) + for item in items: + if item.key == key: + return item + return ValueError('Key "{}" not found in {}.'.format(key, items)) + + +class Block(Element): + + is_block = True + + STATE_LABELS = ['disabled', 'enabled', 'bypassed'] + + key = '' + label = '' + category = '' + flags = Flags('') + documentation = {'': ''} + + value = None + checks = [] + + templates = MakoTemplates() + parameters_data = [] + inputs_data = [] + outputs_data = [] + + extra_data = {} + + # region Init + def __init__(self, parent): + """Make a new block from nested data.""" + super(Block, self).__init__(parent) + self.params = self._init_params() + self.sinks = self._init_ports(self.inputs_data, direction='sink') + self.sources = self._init_ports(self.outputs_data, direction='source') + + self.active_sources = [] # on rewrite + self.active_sinks = [] # on rewrite + + self.states = {'state': True} + + def _init_params(self): + is_dsp_block = not self.flags.not_dsp + has_inputs = bool(self.inputs_data) + has_outputs = bool(self.outputs_data) + + params = collections.OrderedDict() + param_factory = self.parent_platform.make_param + + def add_param(id, **kwargs): + params[id] = param_factory(self, id=id, **kwargs) + + add_param(id='id', name='ID', dtype='id', + hide='none' if (self.key == 'options' or self.is_variable) else 'part') + + if is_dsp_block: + add_param(id='alias', name='Block Alias', dtype='string', + hide='part', category=ADVANCED_PARAM_TAB) + + if has_outputs or has_inputs: + add_param(id='affinity', name='Core Affinity', dtype='int_vector', + hide='part', category=ADVANCED_PARAM_TAB) + + if has_outputs: + add_param(id='minoutbuf', name='Min Output Buffer', dtype='int', + hide='part', value='0', category=ADVANCED_PARAM_TAB) + add_param(id='maxoutbuf', name='Max Output Buffer', dtype='int', + hide='part', value='0', category=ADVANCED_PARAM_TAB) + + base_params_n = {} + for param_data in self.parameters_data: + param_id = param_data['id'] + if param_id in params: + raise Exception('Param id "{}" is not unique'.format(param_id)) + + base_key = param_data.get('base_key', None) + param_data_ext = base_params_n.get(base_key, {}).copy() + param_data_ext.update(param_data) + + add_param(**param_data_ext) + base_params_n[param_id] = param_data_ext + + add_param(id='comment', name='Comment', dtype='_multiline', hide='part', + value='', category=ADVANCED_PARAM_TAB) + return params + + def _init_ports(self, ports_n, direction): + ports = [] + port_factory = self.parent_platform.make_port + port_ids = set() + + def make_stream_port_id(_pool=itertools.count()): + return {'sink': 'in', 'source': 'out'}[direction] + str(next(_pool)) + + for i, port_data in enumerate(ports_n): + port_id = port_data.setdefault('id', make_stream_port_id()) + if port_id in port_ids: + raise Exception('Port id "{}" already exists in {}s'.format(port_id, direction)) + port_ids.add(port_id) + + port = port_factory(parent=self, direction=direction, **port_data) + ports.append(port) + return ports + # endregion + + # region Rewrite_and_Validation + def rewrite(self): + """ + Add and remove ports to adjust for the nports. + """ + Element.rewrite(self) + + def rekey(ports): + """Renumber non-message/message ports""" + domain_specific_port_index = collections.defaultdict(int) + for port in ports: + if not port.key.isdigit(): + continue + domain = port.domain + port.key = str(domain_specific_port_index[domain]) + domain_specific_port_index[domain] += 1 + + # Adjust nports + for ports in (self.sources, self.sinks): + self._rewrite_nports(ports) + rekey(ports) + + # disconnect hidden ports + self.parent_flowgraph.disconnect(*[p for p in self.ports() if p.hidden]) + + self.active_sources = [p for p in self.sources if not p.hidden] + self.active_sinks = [p for p in self.sinks if not p.hidden] + + def _rewrite_nports(self, ports): + for port in ports: + if hasattr(port, 'master_port'): # Not a master port and no left-over clones + continue + nports = port.multiplicity + for clone in port.clones[nports-1:]: + # Remove excess connections + self.parent_flowgraph.disconnect(clone) + port.remove_clone(clone) + ports.remove(clone) + # Add more cloned ports + for j in range(1 + len(port.clones), nports): + clone = port.add_clone() + ports.insert(ports.index(port) + j, clone) + + def validate(self): + """ + Validate this block. + Call the base class validate. + Evaluate the checks: each check must evaluate to True. + """ + Element.validate(self) + self._run_checks() + self._validate_generate_mode_compat() + self._validate_var_value() + + def _run_checks(self): + """Evaluate the checks""" + for check in self.checks: + try: + if not self.evaluate(check): + self.add_error_message('Check "{}" failed.'.format(check)) + except: + self.add_error_message('Check "{}" did not evaluate.'.format(check)) + + def _validate_generate_mode_compat(self): + """check if this is a GUI block and matches the selected generate option""" + current_generate_option = self.parent.get_option('generate_options') + + def check_generate_mode(label, flag, valid_options): + block_requires_mode = ( + flag in self.flags or self.label.upper().startswith(label) + ) + if block_requires_mode and current_generate_option not in valid_options: + self.add_error_message("Can't generate this block in mode: {} ".format( + repr(current_generate_option))) + + check_generate_mode('QT GUI', Flags.NEED_QT_GUI, ('qt_gui', 'hb_qt_gui')) + + def _validate_var_value(self): + """or variables check the value (only if var_value is used)""" + if self.is_variable and self.value != 'value': + try: + self.parent_flowgraph.evaluate(self.value, local_namespace=self.namespace) + except Exception as err: + self.add_error_message('Value "{}" cannot be evaluated:\n{}'.format(self.value, err)) + # endregion + + # region Properties + + def __str__(self): + return 'Block - {} - {}({})'.format(self.name, self.label, self.key) + + def __repr__(self): + try: + name = self.name + except Exception: + name = self.key + return 'block[' + name + ']' + + @property + def name(self): + return self.params['id'].value + + @lazy_property + def is_virtual_or_pad(self): + return self.key in ("virtual_source", "virtual_sink", "pad_source", "pad_sink") + + @lazy_property + def is_variable(self): + return bool(self.value) + + @lazy_property + def is_import(self): + return self.key == 'import' + + @property + def comment(self): + return self.params['comment'].value + + @property + def state(self): + """Gets the block's current state.""" + state = self.states['state'] + return state if state in self.STATE_LABELS else 'enabled' + + @state.setter + def state(self, value): + """Sets the state for the block.""" + self.states['state'] = value + + # Enable/Disable Aliases + @property + def enabled(self): + """Get the enabled state of the block""" + return self.state != 'disabled' + + # endregion + + ############################################## + # Getters (old) + ############################################## + def get_var_make(self): + return self.templates.render('var_make') + + def get_var_value(self): + return self.templates.render('var_value') + + def get_callbacks(self): + """ + Get a list of function callbacks for this block. + + Returns: + a list of strings + """ + def make_callback(callback): + if 'self.' in callback: + return callback + return 'self.{}.{}'.format(self.name, callback) + return [make_callback(c) for c in self.templates.render('callbacks')] + + def is_virtual_sink(self): + return self.key == 'virtual_sink' + + def is_virtual_source(self): + return self.key == 'virtual_source' + + # Block bypassing + def get_bypassed(self): + """ + Check if the block is bypassed + """ + return self.state == 'bypassed' + + def set_bypassed(self): + """ + Bypass the block + + Returns: + True if block chagnes state + """ + if self.state != 'bypassed' and self.can_bypass(): + self.state = 'bypassed' + return True + return False + + def can_bypass(self): + """ Check the number of sinks and sources and see if this block can be bypassed """ + # Check to make sure this is a single path block + # Could possibly support 1 to many blocks + if len(self.sources) != 1 or len(self.sinks) != 1: + return False + if not (self.sources[0].dtype == self.sinks[0].dtype): + return False + if self.flags.disable_bypass: + return False + return True + + def ports(self): + return itertools.chain(self.sources, self.sinks) + + def active_ports(self): + return itertools.chain(self.active_sources, self.active_sinks) + + def children(self): + return itertools.chain(six.itervalues(self.params), self.ports()) + + ############################################## + # Access + ############################################## + + def get_sink(self, key): + return _get_elem(self.sinks, key) + + def get_source(self, key): + return _get_elem(self.sources, key) + + ############################################## + # Resolve + ############################################## + @property + def namespace(self): + return {key: param.get_evaluated() for key, param in six.iteritems(self.params)} + + @property + def namespace_templates(self): + return {key: param.template_arg for key, param in six.iteritems(self.params)} + + def evaluate(self, expr): + return self.parent_flowgraph.evaluate(expr, self.namespace) + + ############################################## + # Import/Export Methods + ############################################## + def export_data(self): + """ + Export this block's params to nested data. + + Returns: + a nested data odict + """ + data = collections.OrderedDict() + if self.key != 'options': + data['name'] = self.name + data['id'] = self.key + data['parameters'] = collections.OrderedDict(sorted( + (param_id, param.value) for param_id, param in self.params.items() + if param_id != 'id' + )) + data['states'] = collections.OrderedDict(sorted(self.states.items())) + return data + + def import_data(self, name, states, parameters, **_): + """ + Import this block's params from nested data. + Any param keys that do not exist will be ignored. + Since params can be dynamically created based another param, + call rewrite, and repeat the load until the params stick. + """ + self.params['id'].value = name + self.states.update(states) + + def get_hash(): + return hash(tuple(hash(v) for v in self.params.values())) + + pre_rewrite_hash = -1 + while pre_rewrite_hash != get_hash(): + for key, value in six.iteritems(parameters): + try: + self.params[key].set_value(value) + except KeyError: + continue + # Store hash and call rewrite + pre_rewrite_hash = get_hash() + self.rewrite() diff --git a/grc/core/blocks/dummy.py b/grc/core/blocks/dummy.py new file mode 100644 index 0000000000..6a5ec2fa72 --- /dev/null +++ b/grc/core/blocks/dummy.py @@ -0,0 +1,54 @@ +# Copyright 2016 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 __future__ import absolute_import + +from . import Block, register_build_in + + +@register_build_in +class DummyBlock(Block): + + is_dummy_block = True + + label = 'Missing Block' + key = '_dummy' + + def __init__(self, parent, missing_block_id, parameters, **_): + self.key = missing_block_id + super(DummyBlock, self).__init__(parent=parent) + + param_factory = self.parent_platform.make_param + for param_id in parameters: + self.params.setdefault(param_id, param_factory(parent=self, id=param_id, dtype='string')) + + def is_valid(self): + return False + + @property + def enabled(self): + return False + + def add_missing_port(self, port_id, direction): + port = self.parent_platform.make_port( + parent=self, direction=direction, id=port_id, name='?', dtype='', + ) + if port.is_source: + self.sources.append(port) + else: + self.sinks.append(port) + return port diff --git a/grc/core/blocks/embedded_python.py b/grc/core/blocks/embedded_python.py new file mode 100644 index 0000000000..0b5a7a21c5 --- /dev/null +++ b/grc/core/blocks/embedded_python.py @@ -0,0 +1,242 @@ +# Copyright 2015-16 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 __future__ import absolute_import + +from ast import literal_eval +from textwrap import dedent + +from . import Block, register_build_in +from ._templates import MakoTemplates + +from .. import utils +from ..base import Element + + +DEFAULT_CODE = '''\ +""" +Embedded Python Blocks: + +Each time this file is saved, GRC will instantiate the first class it finds +to get ports and parameters of your block. The arguments to __init__ will +be the parameters. All of them are required to have default values! +""" + +import numpy as np +from gnuradio import gr + + +class blk(gr.sync_block): # other base classes are basic_block, decim_block, interp_block + """Embedded Python Block example - a simple multiply const""" + + def __init__(self, example_param=1.0): # only default arguments here + """arguments to this function show up as parameters in GRC""" + gr.sync_block.__init__( + self, + name='Embedded Python Block', # will show up in GRC + in_sig=[np.complex64], + out_sig=[np.complex64] + ) + # if an attribute with the same name as a parameter is found, + # a callback is registered (properties work, too). + self.example_param = example_param + + def work(self, input_items, output_items): + """example: multiply with constant""" + output_items[0][:] = input_items[0] * self.example_param + return len(output_items[0]) +''' + +DOC = """ +This block represents an arbitrary GNU Radio Python Block. + +Its source code can be accessed through the parameter 'Code' which opens your editor. \ +Each time you save changes in the editor, GRC will update the block. \ +This includes the number, names and defaults of the parameters, \ +the ports (stream and message) and the block name and documentation. + +Block Documentation: +(will be replaced the docstring of your block class) +""" + + +@register_build_in +class EPyBlock(Block): + + key = 'epy_block' + label = 'Python Block' + documentation = {'': DOC} + + parameters_data = [dict( + label='Code', + id='_source_code', + dtype='_multiline_python_external', + value=DEFAULT_CODE, + hide='part', + )] + inputs_data = [] + outputs_data = [] + + def __init__(self, flow_graph, **kwargs): + super(EPyBlock, self).__init__(flow_graph, **kwargs) + self.states['_io_cache'] = '' + + self._epy_source_hash = -1 + self._epy_reload_error = None + + def rewrite(self): + Element.rewrite(self) + + param_src = self.params['_source_code'] + + src = param_src.get_value() + src_hash = hash((self.name, src)) + if src_hash == self._epy_source_hash: + return + + try: + blk_io = utils.epy_block_io.extract(src) + + except Exception as e: + self._epy_reload_error = ValueError(str(e)) + try: # Load last working block io + blk_io_args = literal_eval(self.states['_io_cache']) + if len(blk_io_args) == 6: + blk_io_args += ([],) # add empty callbacks + blk_io = utils.epy_block_io.BlockIO(*blk_io_args) + except Exception: + return + else: + self._epy_reload_error = None # Clear previous errors + self.states['_io_cache'] = repr(tuple(blk_io)) + + # print "Rewriting embedded python block {!r}".format(self.name) + self._epy_source_hash = src_hash + + self.label = blk_io.name or blk_io.cls + self.documentation = {'': blk_io.doc} + + self.templates['imports'] = 'import ' + self.name + self.templates['make'] = '{mod}.{cls}({args})'.format( + mod=self.name, + cls=blk_io.cls, + args=', '.join('{0}=${{ {0} }}'.format(key) for key, _ in blk_io.params)) + self.templates['callbacks'] = [ + '{0} = ${{ {0} }}'.format(attr) for attr in blk_io.callbacks + ] + + self._update_params(blk_io.params) + self._update_ports('in', self.sinks, blk_io.sinks, 'sink') + self._update_ports('out', self.sources, blk_io.sources, 'source') + + super(EPyBlock, self).rewrite() + + def _update_params(self, params_in_src): + param_factory = self.parent_platform.make_param + params = {} + for param in list(self.params): + if hasattr(param, '__epy_param__'): + params[param.key] = param + del self.params[param.key] + + for id_, value in params_in_src: + try: + param = params[id_] + if param.default == param.value: + param.set_value(value) + param.default = str(value) + except KeyError: # need to make a new param + param = param_factory( + parent=self, id=id_, dtype='raw', value=value, + name=id_.replace('_', ' ').title(), + ) + setattr(param, '__epy_param__', True) + self.params[id_] = param + + def _update_ports(self, label, ports, port_specs, direction): + port_factory = self.parent_platform.make_port + ports_to_remove = list(ports) + iter_ports = iter(ports) + ports_new = [] + port_current = next(iter_ports, None) + for key, port_type, vlen in port_specs: + reuse_port = ( + port_current is not None and + port_current.dtype == port_type and + port_current.vlen == vlen and + (key.isdigit() or port_current.key == key) + ) + if reuse_port: + ports_to_remove.remove(port_current) + port, port_current = port_current, next(iter_ports, None) + else: + n = dict(name=label + str(key), dtype=port_type, id=key) + if port_type == 'message': + n['name'] = key + n['optional'] = '1' + if vlen > 1: + n['vlen'] = str(vlen) + port = port_factory(self, direction=direction, **n) + ports_new.append(port) + # replace old port list with new one + del ports[:] + ports.extend(ports_new) + # remove excess port connections + self.parent_flowgraph.disconnect(*ports_to_remove) + + def validate(self): + super(EPyBlock, self).validate() + if self._epy_reload_error: + self.params['_source_code'].add_error_message(str(self._epy_reload_error)) + + +@register_build_in +class EPyModule(Block): + key = 'epy_module' + label = 'Python Module' + documentation = {'': dedent(""" + This block lets you embed a python module in your flowgraph. + + Code you put in this module is accessible in other blocks using the ID of this + block. Example: + + If you put + + a = 2 + + def double(arg): + return 2 * arg + + in a Python Module Block with the ID 'stuff' you can use code like + + stuff.a # evals to 2 + stuff.double(3) # evals to 6 + + to set parameters of other blocks in your flowgraph. + """)} + + parameters_data = [dict( + label='Code', + id='source_code', + dtype='_multiline_python_external', + value='# this module will be imported in the into your flowgraph', + hide='part', + )] + + templates = MakoTemplates( + imports='import ${ id } # embedded python module', + ) diff --git a/grc/core/blocks/virtual.py b/grc/core/blocks/virtual.py new file mode 100644 index 0000000000..a10853ad1b --- /dev/null +++ b/grc/core/blocks/virtual.py @@ -0,0 +1,76 @@ +# Copyright 2016 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 __future__ import absolute_import + +import itertools + +from . import Block, register_build_in + + +@register_build_in +class VirtualSink(Block): + count = itertools.count() + + key = 'virtual_sink' + label = 'Virtual Sink' + documentation = {'': ''} + + parameters_data = [dict( + label='Stream ID', + id='stream_id', + dtype='stream_id', + )] + inputs_data = [dict( + domain='stream', + dtype='' + )] + + def __init__(self, parent, **kwargs): + super(VirtualSink, self).__init__(parent, **kwargs) + self.params['id'].hide = 'all' + + @property + def stream_id(self): + return self.params['stream_id'].value + + +@register_build_in +class VirtualSource(Block): + count = itertools.count() + + key = 'virtual_source' + label = 'Virtual Source' + documentation = {'': ''} + + parameters_data = [dict( + label='Stream ID', + id='stream_id', + dtype='stream_id', + )] + outputs_data = [dict( + domain='stream', + dtype='' + )] + + def __init__(self, parent, **kwargs): + super(VirtualSource, self).__init__(parent, **kwargs) + self.params['id'].hide = 'all' + + @property + def stream_id(self): + return self.params['stream_id'].value diff --git a/grc/core/errors.py b/grc/core/errors.py new file mode 100644 index 0000000000..6437cc4fa1 --- /dev/null +++ b/grc/core/errors.py @@ -0,0 +1,30 @@ +# Copyright 2016 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 __future__ import absolute_import, print_function + + +class GRCError(Exception): + """Generic error class""" + + +class BlockLoadError(GRCError): + """Error during block loading""" + + +class TemplateError(BlockLoadError): + """Mako Template Error""" diff --git a/grc/core/generator/FlowGraphProxy.py b/grc/core/generator/FlowGraphProxy.py index 23ccf95c4b..f438fa0d39 100644 --- a/grc/core/generator/FlowGraphProxy.py +++ b/grc/core/generator/FlowGraphProxy.py @@ -66,14 +66,15 @@ class FlowGraphProxy(object): # TODO: move this in a refactored Generator self.get_pad_sinks() if direction in ('source', 'out') else [] ports = [] for pad in pads: + type_param = pad.params['type'] master = { - 'label': str(pad.get_param('label').get_evaluated()), - 'type': str(pad.get_param('type').get_evaluated()), - 'vlen': str(pad.get_param('vlen').get_value()), - 'size': pad.get_param('type').opt_value('size'), - 'optional': bool(pad.get_param('optional').get_evaluated()), + 'label': str(pad.params['label'].get_evaluated()), + 'type': str(pad.params['type'].get_evaluated()), + 'vlen': str(pad.params['vlen'].get_value()), + 'size': type_param.options.attributes[type_param.get_value()]['size'], + 'optional': bool(pad.params['optional'].get_evaluated()), } - num_ports = pad.get_param('num_streams').get_evaluated() + num_ports = pad.params['num_streams'].get_evaluated() if num_ports > 1: for i in range(num_ports): clone = master.copy() @@ -91,7 +92,7 @@ class FlowGraphProxy(object): # TODO: move this in a refactored Generator a list of pad source blocks in this flow graph """ pads = [b for b in self.get_enabled_blocks() if b.key == 'pad_source'] - return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id())) + return sorted(pads, key=lambda x: x.name) def get_pad_sinks(self): """ @@ -101,7 +102,7 @@ class FlowGraphProxy(object): # TODO: move this in a refactored Generator a list of pad sink blocks in this flow graph """ pads = [b for b in self.get_enabled_blocks() if b.key == 'pad_sink'] - return sorted(pads, lambda x, y: cmp(x.get_id(), y.get_id())) + return sorted(pads, key=lambda x: x.name) def get_pad_port_global_key(self, port): """ @@ -116,15 +117,46 @@ class FlowGraphProxy(object): # TODO: move this in a refactored Generator for pad in pads: # using the block param 'type' instead of the port domain here # to emphasize that hier block generation is domain agnostic - is_message_pad = pad.get_param('type').get_evaluated() == "message" + is_message_pad = pad.params['type'].get_evaluated() == "message" if port.parent == pad: if is_message_pad: - key = pad.get_param('label').get_value() + key = pad.params['label'].get_value() else: key = str(key_offset + int(port.key)) return key else: # assuming we have either only sources or sinks if not is_message_pad: - key_offset += len(pad.get_ports()) + key_offset += len(pad.sinks) + len(pad.sources) return -1 + + +def get_hier_block_io(flow_graph, direction, domain=None): + """ + Get a list of io ports for this flow graph. + + Returns a list of dicts with: type, label, vlen, size, optional + """ + pads = flow_graph.get_pad_sources() if direction in ('sink', 'in') else \ + flow_graph.get_pad_sinks() if direction in ('source', 'out') else [] + ports = [] + for pad in pads: + type_param = pad.params['type'] + master = { + 'label': str(pad.params['label'].get_evaluated()), + 'type': str(pad.params['type'].get_evaluated()), + 'vlen': str(pad.params['vlen'].get_value()), + 'size': type_param.options.attributes[type_param.get_value()]['size'], + 'optional': bool(pad.params['optional'].get_evaluated()), + } + num_ports = pad.params['num_streams'].get_evaluated() + if num_ports > 1: + for i in range(num_ports): + clone = master.copy() + clone['label'] += str(i) + ports.append(clone) + else: + ports.append(master) + if domain is not None: + ports = [p for p in ports if p.domain == domain] + return ports diff --git a/grc/core/generator/Generator.py b/grc/core/generator/Generator.py index 316ed5014d..62dc26b8a8 100644 --- a/grc/core/generator/Generator.py +++ b/grc/core/generator/Generator.py @@ -18,25 +18,16 @@ from __future__ import absolute_import -import codecs import os -import tempfile -import operator -import collections -from Cheetah.Template import Template -import six +from mako.template import Template -from .FlowGraphProxy import FlowGraphProxy -from .. import ParseXML, Messages -from ..Constants import ( - TOP_BLOCK_FILE_MODE, BLOCK_FLAG_NEED_QT_GUI, - HIER_BLOCK_FILE_MODE, BLOCK_DTD -) -from ..utils import expr_utils +from .hier_block import HierBlockGenerator, QtHierBlockGenerator +from .top_block import TopBlockGenerator DATA_DIR = os.path.dirname(__file__) -FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.tmpl') +FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.py.mako') +flow_graph_template = Template(filename=FLOW_GRAPH_TEMPLATE) class Generator(object): @@ -64,338 +55,3 @@ class Generator(object): def __getattr__(self, item): """get all other attrib from actual generator object""" return getattr(self._generator, item) - - -class TopBlockGenerator(object): - - def __init__(self, flow_graph, file_path): - """ - Initialize the top block generator object. - - Args: - flow_graph: the flow graph object - file_path: the path to write the file to - """ - self._flow_graph = FlowGraphProxy(flow_graph) - self._generate_options = self._flow_graph.get_option('generate_options') - self._mode = TOP_BLOCK_FILE_MODE - dirname = os.path.dirname(file_path) - # Handle the case where the directory is read-only - # In this case, use the system's temp directory - if not os.access(dirname, os.W_OK): - dirname = tempfile.gettempdir() - filename = self._flow_graph.get_option('id') + '.py' - self.file_path = os.path.join(dirname, filename) - self._dirname = dirname - - def write(self): - """generate output and write it to files""" - # Do throttle warning - throttling_blocks = [b for b in self._flow_graph.get_enabled_blocks() - if b.is_throtteling] - if not throttling_blocks and not self._generate_options.startswith('hb'): - Messages.send_warning("This flow graph may not have flow control: " - "no audio or RF hardware blocks found. " - "Add a Misc->Throttle block to your flow " - "graph to avoid CPU congestion.") - if len(throttling_blocks) > 1: - keys = set([b.key for b in throttling_blocks]) - if len(keys) > 1 and 'blocks_throttle' in keys: - Messages.send_warning("This flow graph contains a throttle " - "block and another rate limiting block, " - "e.g. a hardware source or sink. " - "This is usually undesired. Consider " - "removing the throttle block.") - # Generate - for filename, data in self._build_python_code_from_template(): - with codecs.open(filename, 'w', encoding='utf-8') as fp: - fp.write(data) - if filename == self.file_path: - try: - os.chmod(filename, self._mode) - except: - pass - - def _build_python_code_from_template(self): - """ - Convert the flow graph to python code. - - Returns: - a string of python code - """ - output = list() - - fg = self._flow_graph - title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title() - imports = fg.get_imports() - variables = fg.get_variables() - parameters = fg.get_parameters() - monitors = fg.get_monitors() - - # List of blocks not including variables and imports and parameters and disabled - def _get_block_sort_text(block): - code = block.get_make().replace(block.get_id(), ' ') - try: - code += block.get_param('gui_hint').get_value() # Newer gui markup w/ qtgui - except: - pass - return code - - blocks_all = expr_utils.sort_objects( - [b for b in fg.blocks if b.enabled and not b.get_bypassed()], - operator.methodcaller('get_id'), _get_block_sort_text - ) - deprecated_block_keys = set(b.name for b in blocks_all if b.is_deprecated) - for key in deprecated_block_keys: - Messages.send_warning("The block {!r} is deprecated.".format(key)) - - # List of regular blocks (all blocks minus the special ones) - blocks = [b for b in blocks_all if b not in imports and b not in parameters] - - for block in blocks: - key = block.key - file_path = os.path.join(self._dirname, block.get_id() + '.py') - if key == 'epy_block': - src = block.get_param('_source_code').get_value() - output.append((file_path, src)) - elif key == 'epy_module': - src = block.get_param('source_code').get_value() - output.append((file_path, src)) - - # Filter out bus and virtual sink connections - connections = [con for con in fg.get_enabled_connections() - if not (con.is_bus() or con.sink_block.is_virtual_sink())] - - # Get the virtual blocks and resolve their connections - connection_factory = fg.parent_platform.Connection - virtual = [c for c in connections if c.source_block.is_virtual_source()] - for connection in virtual: - sink = connection.sink_port - for source in connection.source_port.resolve_virtual_source(): - resolved = connection_factory(fg.orignal_flowgraph, source, sink) - connections.append(resolved) - # Remove the virtual connection - connections.remove(connection) - - # Bypassing blocks: Need to find all the enabled connections for the block using - # the *connections* object rather than get_connections(). Create new connections - # that bypass the selected block and remove the existing ones. This allows adjacent - # bypassed blocks to see the newly created connections to downstream blocks, - # allowing them to correctly construct bypass connections. - bypassed_blocks = fg.get_bypassed_blocks() - for block in bypassed_blocks: - # Get the upstream connection (off of the sink ports) - # Use *connections* not get_connections() - source_connection = [c for c in connections if c.sink_port == block.sinks[0]] - # The source connection should never have more than one element. - assert (len(source_connection) == 1) - - # Get the source of the connection. - source_port = source_connection[0].source_port - - # Loop through all the downstream connections - for sink in (c for c in connections if c.source_port == block.sources[0]): - if not sink.enabled: - # Ignore disabled connections - continue - connection = connection_factory(fg.orignal_flowgraph, source_port, sink.sink_port) - connections.append(connection) - # Remove this sink connection - connections.remove(sink) - # Remove the source connection - connections.remove(source_connection[0]) - - # List of connections where each endpoint is enabled (sorted by domains, block names) - connections.sort(key=lambda c: ( - c.source_port.domain, c.sink_port.domain, - c.source_block.get_id(), c.sink_block.get_id() - )) - - connection_templates = fg.parent.connection_templates - - # List of variable names - var_ids = [var.get_id() for var in parameters + variables] - replace_dict = dict((var_id, 'self.' + var_id) for var_id in var_ids) - callbacks_all = [] - for block in blocks_all: - callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_callbacks()) - - # Map var id to callbacks - def uses_var_id(): - used = expr_utils.get_variable_dependencies(callback, [var_id]) - return used and 'self.' + var_id in callback # callback might contain var_id itself - - callbacks = {} - for var_id in var_ids: - callbacks[var_id] = [callback for callback in callbacks_all if uses_var_id()] - - # Load the namespace - namespace = { - 'title': title, - 'imports': imports, - 'flow_graph': fg, - 'variables': variables, - 'parameters': parameters, - 'monitors': monitors, - 'blocks': blocks, - 'connections': connections, - 'connection_templates': connection_templates, - 'generate_options': self._generate_options, - 'callbacks': callbacks, - } - # Build the template - t = Template(open(FLOW_GRAPH_TEMPLATE, 'r').read(), namespace) - output.append((self.file_path, "\n".join(line.rstrip() for line in str(t).split("\n")))) - return output - - -class HierBlockGenerator(TopBlockGenerator): - """Extends the top block generator to also generate a block XML file""" - - def __init__(self, flow_graph, file_path): - """ - Initialize the hier block generator object. - - Args: - flow_graph: the flow graph object - file_path: where to write the py file (the xml goes into HIER_BLOCK_LIB_DIR) - """ - TopBlockGenerator.__init__(self, flow_graph, file_path) - platform = flow_graph.parent - - hier_block_lib_dir = platform.config.hier_block_lib_dir - if not os.path.exists(hier_block_lib_dir): - os.mkdir(hier_block_lib_dir) - - self._mode = HIER_BLOCK_FILE_MODE - self.file_path = os.path.join(hier_block_lib_dir, self._flow_graph.get_option('id') + '.py') - self.file_path_xml = self.file_path + '.xml' - - def write(self): - """generate output and write it to files""" - TopBlockGenerator.write(self) - ParseXML.to_file(self._build_block_n_from_flow_graph_io(), self.file_path_xml) - ParseXML.validate_dtd(self.file_path_xml, BLOCK_DTD) - try: - os.chmod(self.file_path_xml, self._mode) - except: - pass - - def _build_block_n_from_flow_graph_io(self): - """ - Generate a block XML nested data from the flow graph IO - - Returns: - a xml node tree - """ - # Extract info from the flow graph - block_key = self._flow_graph.get_option('id') - parameters = self._flow_graph.get_parameters() - - def var_or_value(name): - if name in (p.get_id() for p in parameters): - return "$" + name - return name - - # Build the nested data - block_n = collections.OrderedDict() - block_n['name'] = self._flow_graph.get_option('title') or \ - self._flow_graph.get_option('id').replace('_', ' ').title() - block_n['key'] = block_key - block_n['category'] = self._flow_graph.get_option('category') - block_n['import'] = "from {0} import {0} # grc-generated hier_block".format( - self._flow_graph.get_option('id')) - # Make data - if parameters: - block_n['make'] = '{cls}(\n {kwargs},\n)'.format( - cls=block_key, - kwargs=',\n '.join( - '{key}=${key}'.format(key=param.get_id()) for param in parameters - ), - ) - else: - block_n['make'] = '{cls}()'.format(cls=block_key) - # Callback data - block_n['callback'] = [ - 'set_{key}(${key})'.format(key=param.get_id()) for param in parameters - ] - - # Parameters - block_n['param'] = list() - for param in parameters: - param_n = collections.OrderedDict() - param_n['name'] = param.get_param('label').get_value() or param.get_id() - param_n['key'] = param.get_id() - param_n['value'] = param.get_param('value').get_value() - param_n['type'] = 'raw' - param_n['hide'] = param.get_param('hide').get_value() - block_n['param'].append(param_n) - - # Bus stuff - if self._flow_graph.get_bussink(): - block_n['bus_sink'] = '1' - if self._flow_graph.get_bussrc(): - block_n['bus_source'] = '1' - - # Sink/source ports - for direction in ('sink', 'source'): - block_n[direction] = list() - for port in self._flow_graph.get_hier_block_io(direction): - port_n = collections.OrderedDict() - port_n['name'] = port['label'] - port_n['type'] = port['type'] - if port['type'] != "message": - port_n['vlen'] = var_or_value(port['vlen']) - if port['optional']: - port_n['optional'] = '1' - block_n[direction].append(port_n) - - # More bus stuff - bus_struct_sink = self._flow_graph.get_bus_structure_sink() - if bus_struct_sink: - block_n['bus_structure_sink'] = bus_struct_sink[0].get_param('struct').get_value() - bus_struct_src = self._flow_graph.get_bus_structure_src() - if bus_struct_src: - block_n['bus_structure_source'] = bus_struct_src[0].get_param('struct').get_value() - - # Documentation - block_n['doc'] = "\n".join(field for field in ( - self._flow_graph.get_option('author'), - self._flow_graph.get_option('description'), - self.file_path - ) if field) - block_n['grc_source'] = str(self._flow_graph.grc_file_path) - - n = {'block': block_n} - return n - - -class QtHierBlockGenerator(HierBlockGenerator): - - def _build_block_n_from_flow_graph_io(self): - n = HierBlockGenerator._build_block_n_from_flow_graph_io(self) - block_n = collections.OrderedDict() - - # insert flags after category - for key, value in six.iteritems(n['block']): - block_n[key] = value - if key == 'category': - block_n['flags'] = BLOCK_FLAG_NEED_QT_GUI - - if not block_n['name'].upper().startswith('QT GUI'): - block_n['name'] = 'QT GUI ' + block_n['name'] - - gui_hint_param = collections.OrderedDict() - gui_hint_param['name'] = 'GUI Hint' - gui_hint_param['key'] = 'gui_hint' - gui_hint_param['value'] = '' - gui_hint_param['type'] = 'gui_hint' - gui_hint_param['hide'] = 'part' - block_n['param'].append(gui_hint_param) - - block_n['make'] += ( - "\n#set $win = 'self.%s' % $id" - "\n${gui_hint()($win)}" - ) - - return {'block': block_n} diff --git a/grc/core/generator/flow_graph.py.mako b/grc/core/generator/flow_graph.py.mako new file mode 100644 index 0000000000..484441f00f --- /dev/null +++ b/grc/core/generator/flow_graph.py.mako @@ -0,0 +1,352 @@ +% if not generate_options.startswith('hb'): +#!/usr/bin/env python2 +% endif +# -*- coding: utf-8 -*- +<%def name="indent(code)">${ '\n '.join(str(code).splitlines()) }</%def> +""" +GNU Radio Python Flow Graph + +Title: ${title} +% if flow_graph.get_option('author'): +Author: ${flow_graph.get_option('author')} +% endif +% if flow_graph.get_option('description'): +Description: ${flow_graph.get_option('description')} +% endif +Generated: ${ generated_time } +""" + +% if generate_options == 'qt_gui': +from distutils.version import StrictVersion + +if __name__ == '__main__': + import ctypes + import sys + if sys.platform.startswith('linux'): + try: + x11 = ctypes.cdll.LoadLibrary('libX11.so') + x11.XInitThreads() + except: + print "Warning: failed to XInitThreads()" + +% endif +######################################################## +##Create Imports +######################################################## +% for imp in imports: +##${imp.replace(" # grc-generated hier_block", "")} +${imp} +% endfor +######################################################## +##Create Class +## Write the class declaration for a top or hier block. +## The parameter names are the arguments to __init__. +## Setup the IO signature (hier block only). +######################################################## +<% + class_name = flow_graph.get_option('id') + param_str = ', '.join(['self'] + ['%s=%s'%(param.name, param.templates.render('make')) for param in parameters]) +%>\ +% if generate_options == 'qt_gui': +from gnuradio import qtgui + +class ${class_name}(gr.top_block, Qt.QWidget): + + def __init__(${param_str}): + gr.top_block.__init__(self, "${title}") + Qt.QWidget.__init__(self) + self.setWindowTitle("${title}") + qtgui.util.check_set_qss() + try: + self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc')) + except: + pass + self.top_scroll_layout = Qt.QVBoxLayout() + self.setLayout(self.top_scroll_layout) + self.top_scroll = Qt.QScrollArea() + self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame) + self.top_scroll_layout.addWidget(self.top_scroll) + self.top_scroll.setWidgetResizable(True) + self.top_widget = Qt.QWidget() + self.top_scroll.setWidget(self.top_widget) + self.top_layout = Qt.QVBoxLayout(self.top_widget) + self.top_grid_layout = Qt.QGridLayout() + self.top_layout.addLayout(self.top_grid_layout) + + self.settings = Qt.QSettings("GNU Radio", "${class_name}") + + try: + if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"): + self.restoreGeometry(self.settings.value("geometry").toByteArray()) + else: + self.restoreGeometry(self.settings.value("geometry")) + except: + pass +% elif generate_options == 'no_gui': + +class ${class_name}(gr.top_block): + + def __init__(${param_str}): + gr.top_block.__init__(self, "${title}") +% elif generate_options.startswith('hb'): + <% in_sigs = flow_graph.get_hier_block_stream_io('in') %> + <% out_sigs = flow_graph.get_hier_block_stream_io('out') %> + + +% if generate_options == 'hb_qt_gui': +class ${class_name}(gr.hier_block2, Qt.QWidget): +% else: +class ${class_name}(gr.hier_block2): +% endif +<%def name="make_io_sig(io_sigs)"> + <% size_strs = ['%s*%s'%(io_sig['size'], io_sig['vlen']) for io_sig in io_sigs] %> + % if len(io_sigs) == 0: +gr.io_signature(0, 0, 0)\ + #elif len(${io_sigs}) == 1 +gr.io_signature(1, 1, ${size_strs[0]}) + % else: +gr.io_signaturev(${len(io_sigs)}, ${len(io_sigs)}, [${', '.join(ize_strs)}]) + % endif +</%def> + + def __init__(${param_str}): + gr.hier_block2.__init__( + self, "${ title }", + ${make_io_sig(in_sigs)}, + ${make_io_sig(out_sigs)}, + ) + % for pad in flow_graph.get_hier_block_message_io('in'): + self.message_port_register_hier_in("${ pad['label'] }") + % endfor + % for pad in flow_graph.get_hier_block_message_io('out'): + self.message_port_register_hier_out("${ pad['label'] }") + % endfor + % if generate_options == 'hb_qt_gui': + + Qt.QWidget.__init__(self) + self.top_layout = Qt.QVBoxLayout() + self.top_grid_layout = Qt.QGridLayout() + self.top_layout.addLayout(self.top_grid_layout) + self.setLayout(self.top_layout) + % endif +% endif +% if flow_graph.get_option('thread_safe_setters'): + + self._lock = threading.RLock() +% endif +######################################################## +##Create Parameters +## Set the parameter to a property of self. +######################################################## +% if parameters: + + ${'##################################################'} + # Parameters + ${'##################################################'} +% endif +% for param in parameters: + ${indent(param.get_var_make())} +% endfor +######################################################## +##Create Variables +######################################################## +% if variables: + + ${'##################################################'} + # Variables + ${'##################################################'} +% endif +% for var in variables: + ${indent(var.templates.render('var_make'))} +% endfor + % if blocks: + + ${'##################################################'} + # Blocks + ${'##################################################'} + % endif + % for blk, blk_make in blocks: + ${ indent(blk_make.strip('\n')) } +## % if 'alias' in blk.params and blk.params['alias'].get_evaluated(): +## (self.${blk.name}).set_block_alias("${blk.params['alias'].get_evaluated()}") +## % endif +## % if 'affinity' in blk.params and blk.params['affinity'].get_evaluated(): +## (self.${blk.name}).set_processor_affinity(${blk.params['affinity'].get_evaluated()}) +## % endif +## % if len(blk.sources) > 0 and 'minoutbuf' in blk.params and int(blk.params['minoutbuf'].get_evaluated()) > 0: +## (self.${blk.name}).set_min_output_buffer(${blk.params['minoutbuf'].get_evaluated()}) +## % endif +## % if len(blk.sources) > 0 and 'maxoutbuf' in blk.params and int(blk.params['maxoutbuf'].get_evaluated()) > 0: +## (self.${blk.name}).set_max_output_buffer(${blk.params['maxoutbuf'].get_evaluated()}) +## % endif + % endfor + % if connections: + + ${'##################################################'} + # Connections + ${'##################################################'} + % for connection in connections: + ${ connection.rstrip() } + % endfor + % endif +######################################################## +## QT sink close method reimplementation +######################################################## +% if generate_options == 'qt_gui': + + def closeEvent(self, event): + self.settings = Qt.QSettings("GNU Radio", "${class_name}") + self.settings.setValue("geometry", self.saveGeometry()) + event.accept() + % if flow_graph.get_option('qt_qss_theme'): + + def setStyleSheetFromFile(self, filename): + try: + if not os.path.exists(filename): + filename = os.path.join( + gr.prefix(), "share", "gnuradio", "themes", filename) + with open(filename) as ss: + self.setStyleSheet(ss.read()) + except Exception as e: + print >> sys.stderr, e + % endif +% endif +## +## +## +## Create Callbacks +## Write a set method for this variable that calls the callbacks +######################################################## + % for var in parameters + variables: + + def get_${ var.name }(self): + return self.${ var.name } + + def set_${ var.name }(self, ${ var.name }): + % if flow_graph.get_option('thread_safe_setters'): + with self._lock: + self.${ var.name } = ${ var.name } + % for callback in callbacks[var.name]: + ${ indent(callback) } + % endfor + % else: + self.${ var.name } = ${ var.name } + % for callback in callbacks[var.name]: + ${ indent(callback) } + % endfor + % endif + % endfor +######################################################## +##Create Main +## For top block code, generate a main routine. +## Instantiate the top block and run as gui or cli. +######################################################## +<%def name="make_default(type_, param)"> + % if type_ == 'eng_float': +eng_notation.num_to_str(${param.templates.render('make')}) + % else: +${param.templates.render('make')} + % endif +</%def>\ +% if not generate_options.startswith('hb'): +<% params_eq_list = list() %> +% if parameters: + +<% arg_parser_args = '' %>\ +def argument_parser(): + % if flow_graph.get_option('description'): + <% + arg_parser_args = 'description=description' + %>description = ${repr(flow_graph.get_option('description'))} + % endif + parser = ArgumentParser(${arg_parser_args}) + % for param in parameters: +<% + switches = ['"--{}"'.format(param.name.replace('_', '-'))] + short_id = param.params['short_id'].get_value() + if short_id: + switches.insert(0, '"-{}"'.format(short_id)) + + type_ = param.params['type'].get_value() + if type_: + params_eq_list.append('%s=options.%s' % (param.name, param.name)) + + default = param.templates.render('make') + if type_ == 'eng_float': + default = eng_notation.num_to_str(default) + # FIXME: + if type_ == 'string': + type_ = 'str' + %>\ + % if type_: + parser.add_argument( + ${ ', '.join(switches) }, dest="${param.name}", type=${type_}, default=${ default }, + help="Set ${param.params['label'].get_evaluated() or param.name} [default=%(default)r]") + % endif + % endfor + return parser +% endif + + +def main(top_block_cls=${class_name}, options=None): + % if parameters: + if options is None: + options = argument_parser().parse_args() + % endif + % if flow_graph.get_option('realtime_scheduling'): + if gr.enable_realtime_scheduling() != gr.RT_OK: + print "Error: failed to enable real-time scheduling." + % endif + % if generate_options == 'qt_gui': + + if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"): + style = gr.prefs().get_string('qtgui', 'style', 'raster') + Qt.QApplication.setGraphicsSystem(style) + qapp = Qt.QApplication(sys.argv) + + tb = top_block_cls(${ ', '.join(params_eq_list) }) + % if flow_graph.get_option('run'): + tb.start(${flow_graph.get_option('max_nouts') or ''}) + % endif + % if flow_graph.get_option('qt_qss_theme'): + tb.setStyleSheetFromFile(${ flow_graph.get_option('qt_qss_theme') }) + % endif + tb.show() + + def quitting(): + tb.stop() + tb.wait() + qapp.aboutToQuit.connect(quitting) + % for m in monitors: + if 'en' in m.params: + if m.params['en'].get_value(): + (tb.${m.name}).start() + else: + sys.stderr.write("Monitor '{0}' does not have an enable ('en') parameter.".format("tb.${m.name}")) + % endfor + qapp.exec_() + % elif generate_options == 'no_gui': + tb = top_block_cls(${ ', '.join(params_eq_list) }) + % if flow_graph.get_option('run_options') == 'prompt': + tb.start(${ flow_graph.get_option('max_nouts') or '' }) + % for m in monitors: + (tb.${m.name}).start() + % endfor + try: + raw_input('Press Enter to quit: ') + except EOFError: + pass + tb.stop() + % elif flow_graph.get_option('run_options') == 'run': + tb.start(${flow_graph.get_option('max_nouts') or ''}) + % endif + % for m in monitors: + (tb.${m.name}).start() + % endfor + tb.wait() + % endif + + +if __name__ == '__main__': + main() +% endif diff --git a/grc/core/generator/flow_graph.tmpl b/grc/core/generator/flow_graph.tmpl deleted file mode 100644 index 202362c925..0000000000 --- a/grc/core/generator/flow_graph.tmpl +++ /dev/null @@ -1,420 +0,0 @@ -#if not $generate_options.startswith('hb') -#!/usr/bin/env python2 -#end if -# -*- coding: utf-8 -*- -######################################################## -##Cheetah template - gnuradio_python -## -##@param imports the import statements -##@param flow_graph the flow_graph -##@param variables the variable blocks -##@param parameters the parameter blocks -##@param blocks the signal blocks -##@param connections the connections -##@param generate_options the type of flow graph -##@param callbacks variable id map to callback strings -######################################################## -#def indent($code) -#set $code = '\n '.join(str($code).splitlines()) -$code#slurp -#end def -#import time -#set $DIVIDER = '#'*50 -$DIVIDER -# GNU Radio Python Flow Graph -# Title: $title -#if $flow_graph.get_option('author') -# Author: $flow_graph.get_option('author') -#end if -#if $flow_graph.get_option('description') -# Description: $flow_graph.get_option('description') -#end if -# Generated: $time.ctime() -$DIVIDER -#if $flow_graph.get_option('thread_safe_setters') -import threading -#end if - -#if $generate_options == 'qt_gui' -from distutils.version import StrictVersion -#end if - -## Call XInitThreads as the _very_ first thing. -## After some Qt import, it's too late -#if $generate_options == 'qt_gui' -if __name__ == '__main__': - import ctypes - import sys - if sys.platform.startswith('linux'): - try: - x11 = ctypes.cdll.LoadLibrary('libX11.so') - x11.XInitThreads() - except: - print "Warning: failed to XInitThreads()" - -#end if -# -######################################################## -##Create Imports -######################################################## -#if $flow_graph.get_option('qt_qss_theme') -#set imports = $sorted(set($imports + ["import os", "import sys"])) -#end if -#if any(imp.endswith("# grc-generated hier_block") for imp in $imports) -import os -import sys -#set imports = $filter(lambda i: i not in ("import os", "import sys"), $imports) -sys.path.append(os.environ.get('GRC_HIER_PATH', os.path.expanduser('~/.grc_gnuradio'))) - -#end if -#for $imp in $imports -##$(imp.replace(" # grc-generated hier_block", "")) -$imp -#end for -######################################################## -##Create Class -## Write the class declaration for a top or hier block. -## The parameter names are the arguments to __init__. -## Setup the IO signature (hier block only). -######################################################## -#set $class_name = $flow_graph.get_option('id') -#set $param_str = ', '.join(['self'] + ['%s=%s'%(param.get_id(), param.get_make()) for param in $parameters]) -#if $generate_options == 'qt_gui' -from gnuradio import qtgui - -class $(class_name)(gr.top_block, Qt.QWidget): - - def __init__($param_str): - gr.top_block.__init__(self, "$title") - Qt.QWidget.__init__(self) - self.setWindowTitle("$title") - qtgui.util.check_set_qss() - try: - self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc')) - except: - pass - self.top_scroll_layout = Qt.QVBoxLayout() - self.setLayout(self.top_scroll_layout) - self.top_scroll = Qt.QScrollArea() - self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame) - self.top_scroll_layout.addWidget(self.top_scroll) - self.top_scroll.setWidgetResizable(True) - self.top_widget = Qt.QWidget() - self.top_scroll.setWidget(self.top_widget) - self.top_layout = Qt.QVBoxLayout(self.top_widget) - self.top_grid_layout = Qt.QGridLayout() - self.top_layout.addLayout(self.top_grid_layout) - - self.settings = Qt.QSettings("GNU Radio", "$class_name") - - try: - if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"): - self.restoreGeometry(self.settings.value("geometry").toByteArray()) - else: - self.restoreGeometry(self.settings.value("geometry")) - except: - pass -#elif $generate_options == 'no_gui' - - -class $(class_name)(gr.top_block): - - def __init__($param_str): - gr.top_block.__init__(self, "$title") -#elif $generate_options.startswith('hb') - #set $in_sigs = $flow_graph.get_hier_block_stream_io('in') - #set $out_sigs = $flow_graph.get_hier_block_stream_io('out') - - -#if $generate_options == 'hb_qt_gui' -class $(class_name)(gr.hier_block2, Qt.QWidget): -#else -class $(class_name)(gr.hier_block2): -#end if -#def make_io_sig($io_sigs) - #set $size_strs = ['%s*%s'%(io_sig['size'], io_sig['vlen']) for io_sig in $io_sigs] - #if len($io_sigs) == 0 -gr.io_signature(0, 0, 0)#slurp - #elif len($io_sigs) == 1 -gr.io_signature(1, 1, $size_strs[0])#slurp - #else -gr.io_signaturev($(len($io_sigs)), $(len($io_sigs)), [$(', '.join($size_strs))])#slurp - #end if -#end def - - def __init__($param_str): - gr.hier_block2.__init__( - self, "$title", - $make_io_sig($in_sigs), - $make_io_sig($out_sigs), - ) - #for $pad in $flow_graph.get_hier_block_message_io('in') - self.message_port_register_hier_in("$pad['label']") - #end for - #for $pad in $flow_graph.get_hier_block_message_io('out') - self.message_port_register_hier_out("$pad['label']") - #end for - #if $generate_options == 'hb_qt_gui' - - Qt.QWidget.__init__(self) - self.top_layout = Qt.QVBoxLayout() - self.top_grid_layout = Qt.QGridLayout() - self.top_layout.addLayout(self.top_grid_layout) - self.setLayout(self.top_layout) - #end if -#end if -#if $flow_graph.get_option('thread_safe_setters') - - self._lock = threading.RLock() -#end if -######################################################## -##Create Parameters -## Set the parameter to a property of self. -######################################################## -#if $parameters - - $DIVIDER - # Parameters - $DIVIDER -#end if -#for $param in $parameters - $indent($param.get_var_make()) -#end for -######################################################## -##Create Variables -######################################################## -#if $variables - - $DIVIDER - # Variables - $DIVIDER -#end if -#for $var in $variables - $indent($var.get_var_make()) -#end for -######################################################## -##Create Blocks -######################################################## -#if $blocks - - $DIVIDER - # Blocks - $DIVIDER -#end if -#for $blk in filter(lambda b: b.get_make(), $blocks) - #if $blk in $variables - $indent($blk.get_make()) - #else - self.$blk.get_id() = $indent($blk.get_make()) - #if 'alias' in $blk.params and $blk.params['alias'].get_evaluated() - (self.$blk.get_id()).set_block_alias("$blk.params['alias'].get_evaluated()") - #end if - #if 'affinity' in $blk.params and $blk.params['affinity'].get_evaluated() - (self.$blk.get_id()).set_processor_affinity($blk.params['affinity'].get_evaluated()) - #end if - #if len($blk.sources) > 0 and 'minoutbuf' in $blk.params and int($blk.params['minoutbuf'].get_evaluated()) > 0 - (self.$blk.get_id()).set_min_output_buffer($blk.params['minoutbuf'].get_evaluated()) - #end if - #if len($blk.sources) > 0 and 'maxoutbuf' in $blk.params and int($blk.params['maxoutbuf'].get_evaluated()) > 0 - (self.$blk.get_id()).set_max_output_buffer($blk.params['maxoutbuf'].get_evaluated()) - #end if - #end if -#end for -######################################################## -##Create Connections -## The port name should be the id of the parent block. -## However, port names for IO pads should be self. -######################################################## -#def make_port_sig($port) - #if $port.parent.key in ('pad_source', 'pad_sink') - #set block = 'self' - #set key = $flow_graph.get_pad_port_global_key($port) - #else - #set block = 'self.' + $port.parent.get_id() - #set key = $port.key - #end if - #if not $key.isdigit() - #set key = repr($key) - #end if -($block, $key)#slurp -#end def -#if $connections - - $DIVIDER - # Connections - $DIVIDER -#end if -#for $con in $connections - #set global $source = $con.source_port - #set global $sink = $con.sink_port - #include source=$connection_templates[($source.domain, $sink.domain)] - -#end for -######################################################## -## QT sink close method reimplementation -######################################################## -#if $generate_options == 'qt_gui' - - def closeEvent(self, event): - self.settings = Qt.QSettings("GNU Radio", "$class_name") - self.settings.setValue("geometry", self.saveGeometry()) - event.accept() - #if $flow_graph.get_option('qt_qss_theme') - - def setStyleSheetFromFile(self, filename): - try: - if not os.path.exists(filename): - filename = os.path.join( - gr.prefix(), "share", "gnuradio", "themes", filename) - with open(filename) as ss: - self.setStyleSheet(ss.read()) - except Exception as e: - print >> sys.stderr, e - #end if -#end if -######################################################## -##Create Callbacks -## Write a set method for this variable that calls the callbacks -######################################################## -#for $var in $parameters + $variables - - #set $id = $var.get_id() - def get_$(id)(self): - return self.$id - - def set_$(id)(self, $id): - #if $flow_graph.get_option('thread_safe_setters') - with self._lock: - self.$id = $id - #for $callback in $callbacks[$id] - $indent($callback) - #end for - #else - self.$id = $id - #for $callback in $callbacks[$id] - $indent($callback) - #end for - #end if -#end for -######################################################## -##Create Main -## For top block code, generate a main routine. -## Instantiate the top block and run as gui or cli. -######################################################## -#def make_default($type, $param) - #if $type == 'eng_float' -eng_notation.num_to_str($param.get_make())#slurp - #else -$param.get_make()#slurp - #end if -#end def -#def make_short_id($param) - #set $short_id = $param.get_param('short_id').get_evaluated() - #if $short_id - #set $short_id = '-' + $short_id - #end if -$short_id#slurp -#end def -#if not $generate_options.startswith('hb') -#set $params_eq_list = list() -#if $parameters - - -def argument_parser(): - #set $arg_parser_args = '' - #if $flow_graph.get_option('description') - #set $arg_parser_args = 'description=description' - description = $repr($flow_graph.get_option('description')) - #end if - parser = ArgumentParser($arg_parser_args) - #for $param in $parameters - #set $type = $param.get_param('type').get_value() - #if $type - #silent $params_eq_list.append('%s=options.%s'%($param.get_id(), $param.get_id())) - parser.add_argument( - #if $make_short_id($param) - "$make_short_id($param)", #slurp - #end if - "--$param.get_id().replace('_', '-')", dest="$param.get_id()", type=$type, default=$make_default($type, $param), - help="Set $($param.get_param('label').get_evaluated() or $param.get_id()) [default=%(default)r]") - #end if - #end for - return parser -#end if - - -def main(top_block_cls=$(class_name), options=None): - #if $parameters - if options is None: - options = argument_parser().parse_args() - #end if - #if $flow_graph.get_option('realtime_scheduling') - if gr.enable_realtime_scheduling() != gr.RT_OK: - print "Error: failed to enable real-time scheduling." - #end if - - #if $generate_options == 'qt_gui' - if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"): - style = gr.prefs().get_string('qtgui', 'style', 'raster') - Qt.QApplication.setGraphicsSystem(style) - qapp = Qt.QApplication(sys.argv) - - tb = top_block_cls($(', '.join($params_eq_list))) - #if $flow_graph.get_option('run') - #if $flow_graph.get_option('max_nouts') - tb.start($flow_graph.get_option('max_nouts')) - #else - tb.start() - #end if - #end if - #if $flow_graph.get_option('qt_qss_theme') - tb.setStyleSheetFromFile($repr($flow_graph.get_option('qt_qss_theme'))) - #end if - tb.show() - - def quitting(): - tb.stop() - tb.wait() - qapp.aboutToQuit.connect(quitting) - #for $m in $monitors - if 'en' in $m.params: - if $m.params['en'].get_value(): - (tb.$m.get_id()).start() - else: - sys.stderr.write("Monitor '{0}' does not have an enable ('en') parameter.".format("tb.$m.get_id()")) - #end for - qapp.exec_() - #elif $generate_options == 'no_gui' - tb = top_block_cls($(', '.join($params_eq_list))) - #set $run_options = $flow_graph.get_option('run_options') - #if $run_options == 'prompt' - #if $flow_graph.get_option('max_nouts') - tb.start($flow_graph.get_option('max_nouts')) - #else - tb.start() - #end if - #for $m in $monitors - (tb.$m.get_id()).start() - #end for - try: - raw_input('Press Enter to quit: ') - except EOFError: - pass - tb.stop() - #elif $run_options == 'run' - #if $flow_graph.get_option('max_nouts') - tb.start($flow_graph.get_option('max_nouts')) - #else - tb.start() - #end if - #end if - #for $m in $monitors - (tb.$m.get_id()).start() - #end for - tb.wait() - #end if - - -if __name__ == '__main__': - main() -#end if diff --git a/grc/core/generator/hier_block.py b/grc/core/generator/hier_block.py new file mode 100644 index 0000000000..ab362e0663 --- /dev/null +++ b/grc/core/generator/hier_block.py @@ -0,0 +1,196 @@ +import collections +import os + +import six + +from .top_block import TopBlockGenerator + +from .. import ParseXML, Constants + + +class HierBlockGenerator(TopBlockGenerator): + """Extends the top block generator to also generate a block XML file""" + + def __init__(self, flow_graph, file_path): + """ + Initialize the hier block generator object. + + Args: + flow_graph: the flow graph object + file_path: where to write the py file (the xml goes into HIER_BLOCK_LIB_DIR) + """ + TopBlockGenerator.__init__(self, flow_graph, file_path) + platform = flow_graph.parent + + hier_block_lib_dir = platform.config.hier_block_lib_dir + if not os.path.exists(hier_block_lib_dir): + os.mkdir(hier_block_lib_dir) + + self._mode = Constants.HIER_BLOCK_FILE_MODE + self.file_path = os.path.join(hier_block_lib_dir, self._flow_graph.get_option('id') + '.py') + self.file_path_xml = self.file_path + '.xml' + + def write(self): + """generate output and write it to files""" + TopBlockGenerator.write(self) + ParseXML.to_file(self._build_block_n_from_flow_graph_io(), self.file_path_xml) + ParseXML.validate_dtd(self.file_path_xml, Constants.BLOCK_DTD) + try: + os.chmod(self.file_path_xml, self._mode) + except: + pass + + def _build_block_n_from_flow_graph_io(self): + """ + Generate a block XML nested data from the flow graph IO + + Returns: + a xml node tree + """ + # Extract info from the flow graph + block_id = self._flow_graph.get_option('id') + parameters = self._flow_graph.get_parameters() + + def var_or_value(name): + if name in (p.name for p in parameters): + return "${" + name + " }" + return name + + # Build the nested data + data = collections.OrderedDict() + data['id'] = block_id + data['label'] = ( + self._flow_graph.get_option('title') or + self._flow_graph.get_option('id').replace('_', ' ').title() + ) + data['category'] = self._flow_graph.get_option('category') + + # Parameters + data['parameters'] = [] + for param_block in parameters: + p = collections.OrderedDict() + p['id'] = param_block.name + p['label'] = param_block.params['label'].get_value() or param_block.name + p['dtype'] = 'raw' + p['value'] = param_block.params['value'].get_value() + p['hide'] = param_block.params['hide'].get_value() + data['param'].append(p) + + # Ports + for direction in ('inputs', 'outputs'): + data[direction] = [] + for port in get_hier_block_io(self._flow_graph, direction): + p = collections.OrderedDict() + if port.domain == Constants.GR_MESSAGE_DOMAIN: + p['id'] = port.id + p['label'] = port.label + if port.domain != Constants.DEFAULT_DOMAIN: + p['domain'] = port.domain + p['dtype'] = port.dtype + if port.domain != Constants.GR_MESSAGE_DOMAIN: + p['vlen'] = var_or_value(port.vlen) + if port.optional: + p['optional'] = True + data[direction].append(p) + + t = data['templates'] = collections.OrderedDict() + + t['import'] = "from {0} import {0} # grc-generated hier_block".format( + self._flow_graph.get_option('id')) + # Make data + if parameters: + t['make'] = '{cls}(\n {kwargs},\n)'.format( + cls=block_id, + kwargs=',\n '.join( + '{key}=${key}'.format(key=param.name) for param in parameters + ), + ) + else: + t['make'] = '{cls}()'.format(cls=block_id) + # Callback data + t['callback'] = [ + 'set_{key}(${key})'.format(key=param_block.name) for param_block in parameters + ] + + + # Documentation + data['doc'] = "\n".join(field for field in ( + self._flow_graph.get_option('author'), + self._flow_graph.get_option('description'), + self.file_path + ) if field) + data['grc_source'] = str(self._flow_graph.grc_file_path) + + n = {'block': data} + return n + + +class QtHierBlockGenerator(HierBlockGenerator): + + def _build_block_n_from_flow_graph_io(self): + n = HierBlockGenerator._build_block_n_from_flow_graph_io(self) + block_n = collections.OrderedDict() + + # insert flags after category + for key, value in six.iteritems(n['block']): + block_n[key] = value + if key == 'category': + block_n['flags'] = 'need_qt_gui' + + if not block_n['name'].upper().startswith('QT GUI'): + block_n['name'] = 'QT GUI ' + block_n['name'] + + gui_hint_param = collections.OrderedDict() + gui_hint_param['name'] = 'GUI Hint' + gui_hint_param['key'] = 'gui_hint' + gui_hint_param['value'] = '' + gui_hint_param['type'] = 'gui_hint' + gui_hint_param['hide'] = 'part' + block_n['param'].append(gui_hint_param) + + block_n['make'] += ( + "\n#set $win = 'self.%s' % $id" + "\n${gui_hint()($win)}" + ) + + return {'block': block_n} + + +def get_hier_block_io(flow_graph, direction, domain=None): + """ + Get a list of io ports for this flow graph. + + Returns a list of dicts with: type, label, vlen, size, optional + """ + pads = flow_graph.get_pad_sources() if direction == 'inputs' else flow_graph.get_pad_sinks() + + ports = [] + for pad in pads: + for port in (pad.sources if direction == 'inputs' else pad.sinks): + if domain and port.domain != domain: + continue + yield port + + type_param = pad.params['type'] + master = { + 'label': str(pad.params['label'].get_evaluated()), + 'type': str(pad.params['type'].get_evaluated()), + 'vlen': str(pad.params['vlen'].get_value()), + 'size': type_param.options.attributes[type_param.get_value()]['size'], + 'optional': bool(pad.params['optional'].get_evaluated()), + } + if domain and pad. + + num_ports = pad.params['num_streams'].get_evaluated() + if num_ports <= 1: + yield master + else: + for i in range(num_ports): + clone = master.copy() + clone['label'] += str(i) + ports.append(clone) + else: + ports.append(master) + if domain is not None: + ports = [p for p in ports if p.domain == domain] + return ports diff --git a/grc/core/generator/top_block.py b/grc/core/generator/top_block.py new file mode 100644 index 0000000000..e4fed838ae --- /dev/null +++ b/grc/core/generator/top_block.py @@ -0,0 +1,285 @@ +import codecs +import operator +import os +import tempfile +import textwrap +import time + +from mako.template import Template + +from .. import Messages, blocks +from ..Constants import TOP_BLOCK_FILE_MODE +from .FlowGraphProxy import FlowGraphProxy +from ..utils import expr_utils + +DATA_DIR = os.path.dirname(__file__) +FLOW_GRAPH_TEMPLATE = os.path.join(DATA_DIR, 'flow_graph.py.mako') +flow_graph_template = Template(filename=FLOW_GRAPH_TEMPLATE) + + +class TopBlockGenerator(object): + + def __init__(self, flow_graph, file_path): + """ + Initialize the top block generator object. + + Args: + flow_graph: the flow graph object + file_path: the path to write the file to + """ + + self._flow_graph = FlowGraphProxy(flow_graph) + self._generate_options = self._flow_graph.get_option('generate_options') + + self._mode = TOP_BLOCK_FILE_MODE + dirname = os.path.dirname(file_path) + # Handle the case where the directory is read-only + # In this case, use the system's temp directory + if not os.access(dirname, os.W_OK): + dirname = tempfile.gettempdir() + filename = self._flow_graph.get_option('id') + '.py' + self.file_path = os.path.join(dirname, filename) + self._dirname = dirname + + def _warnings(self): + throttling_blocks = [b for b in self._flow_graph.get_enabled_blocks() + if b.flags.throttle] + if not throttling_blocks and not self._generate_options.startswith('hb'): + Messages.send_warning("This flow graph may not have flow control: " + "no audio or RF hardware blocks found. " + "Add a Misc->Throttle block to your flow " + "graph to avoid CPU congestion.") + if len(throttling_blocks) > 1: + keys = set([b.key for b in throttling_blocks]) + if len(keys) > 1 and 'blocks_throttle' in keys: + Messages.send_warning("This flow graph contains a throttle " + "block and another rate limiting block, " + "e.g. a hardware source or sink. " + "This is usually undesired. Consider " + "removing the throttle block.") + + deprecated_block_keys = {b.name for b in self._flow_graph.get_enabled_blocks() if b.flags.deprecated} + for key in deprecated_block_keys: + Messages.send_warning("The block {!r} is deprecated.".format(key)) + + def write(self): + """generate output and write it to files""" + self._warnings() + + for filename, data in self._build_python_code_from_template(): + with codecs.open(filename, 'w', encoding='utf-8') as fp: + fp.write(data) + if filename == self.file_path: + try: + os.chmod(filename, self._mode) + except: + pass + + def _build_python_code_from_template(self): + """ + Convert the flow graph to python code. + + Returns: + a string of python code + """ + output = [] + + fg = self._flow_graph + title = fg.get_option('title') or fg.get_option('id').replace('_', ' ').title() + variables = fg.get_variables() + parameters = fg.get_parameters() + monitors = fg.get_monitors() + + for block in fg.iter_enabled_blocks(): + key = block.key + file_path = os.path.join(self._dirname, block.name + '.py') + if key == 'epy_block': + src = block.params['_source_code'].get_value() + output.append((file_path, src)) + elif key == 'epy_module': + src = block.params['source_code'].get_value() + output.append((file_path, src)) + + namespace = { + 'flow_graph': fg, + 'variables': variables, + 'parameters': parameters, + 'monitors': monitors, + 'generate_options': self._generate_options, + 'generated_time': time.ctime(), + } + flow_graph_code = flow_graph_template.render( + title=title, + imports=self._imports(), + blocks=self._blocks(), + callbacks=self._callbacks(), + connections=self._connections(), + **namespace + ) + # strip trailing white-space + flow_graph_code = "\n".join(line.rstrip() for line in flow_graph_code.split("\n")) + output.append((self.file_path, flow_graph_code)) + + return output + + def _imports(self): + fg = self._flow_graph + imports = fg.imports() + seen = set() + output = [] + + need_path_hack = any(imp.endswith("# grc-generated hier_block") for imp in imports) + if need_path_hack: + output.insert(0, textwrap.dedent("""\ + import os + import sys + sys.path.append(os.environ.get('GRC_HIER_PATH', os.path.expanduser('~/.grc_gnuradio'))) + """)) + seen.add('import os') + seen.add('import sys') + + if fg.get_option('qt_qss_theme'): + imports.append('import os') + imports.append('import sys') + + if fg.get_option('thread_safe_setters'): + imports.append('import threading') + + def is_duplicate(l): + if l.startswith('import') or l.startswith('from') and l in seen: + return True + seen.add(line) + return False + + for import_ in sorted(imports): + lines = import_.strip().split('\n') + if not lines[0]: + continue + for line in lines: + line = line.rstrip() + if not is_duplicate(line): + output.append(line) + + return output + + def _blocks(self): + fg = self._flow_graph + parameters = fg.get_parameters() + + # List of blocks not including variables and imports and parameters and disabled + def _get_block_sort_text(block): + code = block.templates.render('make').replace(block.name, ' ') + try: + code += block.params['gui_hint'].get_value() # Newer gui markup w/ qtgui + except: + pass + return code + + 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') + ] + + blocks = expr_utils.sort_objects(blocks, operator.attrgetter('name'), _get_block_sort_text) + blocks_make = [] + for block in blocks: + make = block.templates.render('make') + if not block.is_variable: + make = 'self.' + block.name + ' = ' + make + if make: + blocks_make.append((block, make)) + return blocks_make + + def _callbacks(self): + fg = self._flow_graph + variables = fg.get_variables() + parameters = fg.get_parameters() + + # List of variable names + var_ids = [var.name for var in parameters + variables] + replace_dict = dict((var_id, 'self.' + var_id) for var_id in var_ids) + callbacks_all = [] + for block in fg.iter_enabled_blocks(): + callbacks_all.extend(expr_utils.expr_replace(cb, replace_dict) for cb in block.get_callbacks()) + + # Map var id to callbacks + def uses_var_id(): + used = expr_utils.get_variable_dependencies(callback, [var_id]) + return used and 'self.' + var_id in callback # callback might contain var_id itself + + callbacks = {} + for var_id in var_ids: + callbacks[var_id] = [callback for callback in callbacks_all if uses_var_id()] + + return callbacks + + def _connections(self): + fg = self._flow_graph + templates = {key: Template(text) + for key, text in fg.parent_platform.connection_templates.items()} + + def make_port_sig(port): + if port.parent.key in ('pad_source', 'pad_sink'): + block = 'self' + key = fg.get_pad_port_global_key(port) + else: + block = 'self.' + port.parent_block.name + key = port.key + + if not key.isdigit(): + key = repr(key) + + return '({block}, {key})'.format(block=block, key=key) + + connections = fg.get_enabled_connections() + + # Get the virtual blocks and resolve their connections + connection_factory = fg.parent_platform.Connection + virtual = [c for c in connections if isinstance(c.source_block, blocks.VirtualSource)] + for connection in virtual: + sink = connection.sink_port + for source in connection.source_port.resolve_virtual_source(): + resolved = connection_factory(fg.orignal_flowgraph, source, sink) + connections.append(resolved) + # Remove the virtual connection + connections.remove(connection) + + # Bypassing blocks: Need to find all the enabled connections for the block using + # the *connections* object rather than get_connections(). Create new connections + # that bypass the selected block and remove the existing ones. This allows adjacent + # bypassed blocks to see the newly created connections to downstream blocks, + # allowing them to correctly construct bypass connections. + bypassed_blocks = fg.get_bypassed_blocks() + for block in bypassed_blocks: + # Get the upstream connection (off of the sink ports) + # Use *connections* not get_connections() + source_connection = [c for c in connections if c.sink_port == block.sinks[0]] + # The source connection should never have more than one element. + assert (len(source_connection) == 1) + + # Get the source of the connection. + source_port = source_connection[0].source_port + + # Loop through all the downstream connections + for sink in (c for c in connections if c.source_port == block.sources[0]): + if not sink.enabled: + # Ignore disabled connections + continue + connection = connection_factory(fg.orignal_flowgraph, source_port, sink.sink_port) + connections.append(connection) + # Remove this sink connection + connections.remove(sink) + # Remove the source connection + connections.remove(source_connection[0]) + + # List of connections where each endpoint is enabled (sorted by domains, block names) + def by_domain_and_blocks(c): + return c.type, c.source_block.name, c.sink_block.name + + rendered = [] + for con in sorted(connections, key=by_domain_and_blocks): + template = templates[con.type] + code = template.render(make_port_sig=make_port_sig, source=con.source_port, sink=con.sink_port) + rendered.append(code) + + return rendered diff --git a/grc/core/io/__init__.py b/grc/core/io/__init__.py new file mode 100644 index 0000000000..f77f1a6704 --- /dev/null +++ b/grc/core/io/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2017 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 diff --git a/grc/core/io/yaml.py b/grc/core/io/yaml.py new file mode 100644 index 0000000000..29b4cb81d6 --- /dev/null +++ b/grc/core/io/yaml.py @@ -0,0 +1,91 @@ +# Copyright 2016 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 __future__ import absolute_import + +from collections import OrderedDict + +import six +import yaml + + +class GRCDumper(yaml.SafeDumper): + @classmethod + def add(cls, data_type): + def decorator(func): + cls.add_representer(data_type, func) + return func + return decorator + + def represent_ordered_mapping(self, data): + value = [] + node = yaml.MappingNode(u'tag:yaml.org,2002:map', value, flow_style=False) + + if self.alias_key is not None: + self.represented_objects[self.alias_key] = node + + for item_key, item_value in six.iteritems(data): + node_key = self.represent_data(item_key) + node_value = self.represent_data(item_value) + value.append((node_key, node_value)) + + return node + + def represent_ordered_mapping_flowing(self, data): + node = self.represent_ordered_mapping(data) + node.flow_style = True + return node + + def represent_list_flowing(self, data): + node = self.represent_list(data) + node.flow_style = True + return node + + def represent_ml_string(self, data): + node = self.represent_str(data) + node.style = '|' + return node + + +class OrderedDictFlowing(OrderedDict): + pass + + +class ListFlowing(list): + pass + + +class MultiLineString(str): + pass + + +GRCDumper.add_representer(OrderedDict, GRCDumper.represent_ordered_mapping) +GRCDumper.add_representer(OrderedDictFlowing, GRCDumper.represent_ordered_mapping_flowing) +GRCDumper.add_representer(ListFlowing, GRCDumper.represent_list_flowing) +GRCDumper.add_representer(tuple, GRCDumper.represent_list) +GRCDumper.add_representer(MultiLineString, GRCDumper.represent_ml_string) +GRCDumper.add_representer(yaml.nodes.ScalarNode, lambda r, n: n) + + +def dump(data, stream=None, **kwargs): + config = dict(stream=stream, default_flow_style=False, indent=4, Dumper=GRCDumper) + config.update(kwargs) + return yaml.dump_all([data], **config) + + +safe_load = yaml.safe_load +__with_libyaml__ = yaml.__with_libyaml__ diff --git a/grc/core/platform.py b/grc/core/platform.py new file mode 100644 index 0000000000..02203942f9 --- /dev/null +++ b/grc/core/platform.py @@ -0,0 +1,430 @@ +# Copyright 2008-2016 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 __future__ import absolute_import, print_function + +from codecs import open +from collections import namedtuple +import glob +import os +import logging +from itertools import chain + +import six +from six.moves import range + +from . import ( + Messages, Constants, + blocks, ports, errors, utils, schema_checker +) + +from .Config import Config +from .base import Element +from .io import yaml +from .generator import Generator +from .FlowGraph import FlowGraph +from .Connection import Connection +from .Param import Param + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + + +class Platform(Element): + + def __init__(self, *args, **kwargs): + """ Make a platform for GNU Radio """ + Element.__init__(self, parent=None) + + self.config = self.Config(*args, **kwargs) + self.block_docstrings = {} + self.block_docstrings_loaded_callback = lambda: None # dummy to be replaced by BlockTreeWindow + + self._docstring_extractor = utils.extract_docs.SubprocessLoader( + callback_query_result=self._save_docstring_extraction_result, + callback_finished=lambda: self.block_docstrings_loaded_callback() + ) + + self.blocks = self.block_classes + self.domains = {} + self.connection_templates = {} + + self._block_categories = {} + self._auto_hier_block_generate_chain = set() + + if not yaml.__with_libyaml__: + logger.warning("Slow YAML loading (libyaml not available)") + + def __str__(self): + return 'Platform - {}'.format(self.config.name) + + @staticmethod + def find_file_in_paths(filename, paths, cwd): + """Checks the provided paths relative to cwd for a certain filename""" + if not os.path.isdir(cwd): + cwd = os.path.dirname(cwd) + if isinstance(paths, str): + paths = (p for p in paths.split(':') if p) + + for path in paths: + path = os.path.expanduser(path) + if not os.path.isabs(path): + path = os.path.normpath(os.path.join(cwd, path)) + file_path = os.path.join(path, filename) + if os.path.exists(os.path.normpath(file_path)): + return file_path + + def load_and_generate_flow_graph(self, file_path, out_path=None, hier_only=False): + """Loads a flow graph from file and generates it""" + Messages.set_indent(len(self._auto_hier_block_generate_chain)) + Messages.send('>>> Loading: {}\n'.format(file_path)) + if file_path in self._auto_hier_block_generate_chain: + Messages.send(' >>> Warning: cyclic hier_block dependency\n') + return None, None + self._auto_hier_block_generate_chain.add(file_path) + try: + flow_graph = self.make_flow_graph() + flow_graph.grc_file_path = file_path + # Other, nested hier_blocks might be auto-loaded here + flow_graph.import_data(self.parse_flow_graph(file_path)) + flow_graph.rewrite() + flow_graph.validate() + if not flow_graph.is_valid(): + raise Exception('Flowgraph invalid') + if hier_only and not flow_graph.get_option('generate_options').startswith('hb'): + raise Exception('Not a hier block') + except Exception as e: + Messages.send('>>> Load Error: {}: {}\n'.format(file_path, str(e))) + return None, None + finally: + self._auto_hier_block_generate_chain.discard(file_path) + Messages.set_indent(len(self._auto_hier_block_generate_chain)) + + try: + generator = self.Generator(flow_graph, out_path or file_path) + Messages.send('>>> Generating: {}\n'.format(generator.file_path)) + generator.write() + except Exception as e: + Messages.send('>>> Generate Error: {}: {}\n'.format(file_path, str(e))) + return None, None + + if flow_graph.get_option('generate_options').startswith('hb'): + # self.load_block_xml(generator.file_path_xml) + # TODO: implement yml output for hier blocks + pass + return flow_graph, generator.file_path + + def build_library(self, path=None): + """load the blocks and block tree from the search paths + + path: a list of paths/files to search in or load (defaults to config) + """ + self._docstring_extractor.start() + + # Reset + self.blocks.clear() + self.domains.clear() + self.connection_templates.clear() + self._block_categories.clear() + + # FIXME: remove this as soon as converter is stable + from ..converter import Converter + converter = Converter(self.config.block_paths, self.config.yml_block_cache) + converter.run() + logging.info('XML converter done.') + + for file_path in self._iter_files_in_block_path(path): + try: + data = converter.cache[file_path] + except KeyError: + with open(file_path, encoding='utf-8') as fp: + data = yaml.safe_load(fp) + + if file_path.endswith('.block.yml'): + loader = self.load_block_description + scheme = schema_checker.BLOCK_SCHEME + elif file_path.endswith('.domain.yml'): + loader = self.load_domain_description + scheme = schema_checker.DOMAIN_SCHEME + elif file_path.endswith('.tree.yml'): + loader = self.load_category_tree_description + scheme = None + else: + continue + + try: + checker = schema_checker.Validator(scheme) + passed = checker.run(data) + for msg in checker.messages: + logger.warning('{:<40s} {}'.format(os.path.basename(file_path), msg)) + if not passed: + logger.info('YAML schema check failed for: ' + file_path) + + loader(data, file_path) + except Exception as error: + logger.exception('Error while loading %s', file_path) + logger.exception(error) + raise + + for key, block in six.iteritems(self.blocks): + category = self._block_categories.get(key, block.category) + if not category: + continue + root = category[0] + if root.startswith('[') and root.endswith(']'): + category[0] = root[1:-1] + else: + category.insert(0, Constants.DEFAULT_BLOCK_MODULE_NAME) + block.category = category + + self._docstring_extractor.finish() + # self._docstring_extractor.wait() + + def _iter_files_in_block_path(self, path=None, ext='yml'): + """Iterator for block descriptions and category trees""" + for entry in (path or self.config.block_paths): + if os.path.isfile(entry): + yield entry + elif os.path.isdir(entry): + pattern = os.path.join(entry, '**.' + ext) + yield_from = glob.iglob(pattern) + for file_path in yield_from: + yield file_path + else: + logger.debug('Ignoring invalid path entry %r', entry) + + def _save_docstring_extraction_result(self, block_id, docstrings): + docs = {} + for match, docstring in six.iteritems(docstrings): + if not docstring or match.endswith('_sptr'): + continue + docs[match] = docstring.replace('\n\n', '\n').strip() + try: + self.blocks[block_id].documentation.update(docs) + except KeyError: + pass # in tests platform might be gone... + + ############################################## + # Description File Loaders + ############################################## + # region loaders + def load_block_description(self, data, file_path): + log = logger.getChild('block_loader') + + # don't load future block format versions + file_format = data['file_format'] + if file_format < 1 or file_format > Constants.BLOCK_DESCRIPTION_FILE_FORMAT_VERSION: + log.error('Unknown format version %d in %s', file_format, file_path) + return + + block_id = data.pop('id').rstrip('_') + + if block_id in self.block_classes_build_in: + log.warning('Not overwriting build-in block %s with %s', block_id, file_path) + return + if block_id in self.blocks: + log.warning('Block with id "%s" overwritten by %s', block_id, file_path) + + try: + block_cls = self.blocks[block_id] = self.new_block_class(block_id, **data) + except errors.BlockLoadError as error: + log.error('Unable to load block %s', block_id) + log.exception(error) + return + + self._docstring_extractor.query( + block_id, block_cls.templates['imports'], block_cls.templates['make'], + ) + + def load_domain_description(self, data, file_path): + log = logger.getChild('domain_loader') + domain_id = data['id'] + if domain_id in self.domains: # test against repeated keys + log.debug('Domain "{}" already exists. Ignoring: %s', file_path) + return + + color = data.get('color', '') + if color.startswith('#'): + try: + tuple(int(color[o:o + 2], 16) / 255.0 for o in range(1, 6, 2)) + except ValueError: + log.warning('Cannot parse color code "%s" in %s', color, file_path) + return + + self.domains[domain_id] = self.Domain( + name=data.get('label', domain_id), + multi_in=data.get('multiple_connections_per_input', True), + multi_out=data.get('multiple_connections_per_output', False), + color=color + ) + for connection in data.get('templates', []): + try: + source_id, sink_id = connection.get('type', []) + except ValueError: + log.warn('Invalid connection template.') + continue + connection_id = str(source_id), str(sink_id) + self.connection_templates[connection_id] = connection.get('connect', '') + + def load_category_tree_description(self, data, file_path): + """Parse category tree file and add it to list""" + log = logger.getChild('tree_loader') + log.debug('Loading %s', file_path) + path = [] + + def load_category(name, elements): + if not isinstance(name, str): + log.debug('invalid name %r', name) + return + if isinstance(elements, list): + pass + elif isinstance(elements, str): + elements = [elements] + else: + log.debug('Ignoring elements of %s', name) + return + path.append(name) + for element in elements: + if isinstance(element, str): + block_id = element + self._block_categories[block_id] = list(path) + elif isinstance(element, dict): + load_category(*next(six.iteritems(element))) + else: + log.debug('Ignoring some elements of %s', name) + path.pop() + + try: + module_name, categories = next(six.iteritems(data)) + except (AttributeError, StopIteration): + log.warning('no valid data found') + else: + load_category(module_name, categories) + + ############################################## + # Access + ############################################## + def parse_flow_graph(self, filename): + """ + Parse a saved flow graph file. + Ensure that the file exists, and passes the dtd check. + + Args: + filename: the flow graph file + + Returns: + nested data + @throws exception if the validation fails + """ + filename = filename or self.config.default_flow_graph + with open(filename, encoding='utf-8') as fp: + is_xml = '<flow_graph>' in fp.read(100) + fp.seek(0) + # todo: try + if not is_xml: + data = yaml.safe_load(fp) + validator = schema_checker.Validator(schema_checker.FLOW_GRAPH_SCHEME) + validator.run(data) + else: + Messages.send('>>> Converting from XML\n') + from ..converter.flow_graph import from_xml + data = from_xml(fp) + + return data + + def save_flow_graph(self, filename, flow_graph): + data = flow_graph.export_data() + + try: + data['connections'] = [yaml.ListFlowing(i) for i in data['connections']] + except KeyError: + pass + + try: + for d in chain([data['options']], data['blocks']): + d['states']['coordinate'] = yaml.ListFlowing(d['states']['coordinate']) + for param_id, value in list(d['parameters'].items()): + if value == '': + d['parameters'].pop(param_id) + except KeyError: + pass + + out = yaml.dump(data, indent=2) + + replace = [ + ('blocks:', '\nblocks:'), + ('connections:', '\nconnections:'), + ('metadata:', '\nmetadata:'), + ] + for r in replace: + out = out.replace(*r) + + with open(filename, 'w', encoding='utf-8') as fp: + fp.write(out) + + def get_generate_options(self): + for param in self.block_classes['options'].parameters_data: + if param.get('id') == 'generate_options': + break + else: + return [] + generate_mode_default = param.get('default') + return [(value, name, value == generate_mode_default) + for value, name in zip(param['options'], param['option_labels'])] + + ############################################## + # Factories + ############################################## + Config = Config + Domain = namedtuple('Domain', 'name multi_in multi_out color') + Generator = Generator + FlowGraph = FlowGraph + Connection = Connection + + block_classes_build_in = blocks.build_ins + block_classes = utils.backports.ChainMap({}, block_classes_build_in) # separates build-in from loaded blocks) + + port_classes = { + None: ports.Port, # default + 'clone': ports.PortClone, # clone of ports with multiplicity > 1 + } + param_classes = { + None: Param, # default + } + + def make_flow_graph(self, from_filename=None): + fg = self.FlowGraph(parent=self) + if from_filename: + data = self.parse_flow_graph(from_filename) + fg.grc_file_path = from_filename + fg.import_data(data) + return fg + + def new_block_class(self, block_id, **data): + return blocks.build(block_id, **data) + + def make_block(self, parent, block_id, **kwargs): + cls = self.block_classes[block_id] + return cls(parent, **kwargs) + + def make_param(self, parent, **kwargs): + cls = self.param_classes[kwargs.pop('cls_key', None)] + return cls(parent, **kwargs) + + def make_port(self, parent, **kwargs): + cls = self.port_classes[kwargs.pop('cls_key', None)] + return cls(parent, **kwargs) diff --git a/grc/core/domain.dtd b/grc/core/ports/__init__.py index b5b0b8bf39..375b5d63e3 100644 --- a/grc/core/domain.dtd +++ b/grc/core/ports/__init__.py @@ -1,5 +1,5 @@ -<!-- -Copyright 2014 Free Software Foundation, Inc. +""" +Copyright 2008-2015 Free Software Foundation, Inc. This file is part of GNU Radio GNU Radio Companion is free software; you can redistribute it and/or @@ -15,21 +15,9 @@ 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 ---> -<!ELEMENT domain (name, key, color?, multiple_sinks?, multiple_sources?, connection*)> -<!-- - Sub level elements. - --> -<!ELEMENT connection (source_domain, sink_domain, make)> -<!-- - Bottom level elements. - Character data only. - --> -<!ELEMENT name (#PCDATA)> -<!ELEMENT key (#PCDATA)> -<!ELEMENT multiple_sinks (#PCDATA)> -<!ELEMENT multiple_sources (#PCDATA)> -<!ELEMENT color (#PCDATA)> -<!ELEMENT make (#PCDATA)> -<!ELEMENT source_domain (#PCDATA)> -<!ELEMENT sink_domain (#PCDATA)> +""" + +from __future__ import absolute_import + +from .port import Port +from .clone import PortClone diff --git a/grc/core/ports/_virtual_connections.py b/grc/core/ports/_virtual_connections.py new file mode 100644 index 0000000000..45f4a247fd --- /dev/null +++ b/grc/core/ports/_virtual_connections.py @@ -0,0 +1,126 @@ +# Copyright 2008-2016 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 __future__ import absolute_import + +from itertools import chain + +from .. import blocks + + +class LoopError(Exception): + pass + + +def upstream_ports(port): + if port.is_sink: + return _sources_from_virtual_sink_port(port) + else: + return _sources_from_virtual_source_port(port) + + +def _sources_from_virtual_sink_port(sink_port, _traversed=None): + """ + Resolve the source port that is connected to the given virtual sink port. + Use the get source from virtual source to recursively resolve subsequent ports. + """ + source_ports_per_virtual_connection = ( + # there can be multiple ports per virtual connection + _sources_from_virtual_source_port(c.source_port, _traversed) # type: list + for c in sink_port.connections(enabled=True) + ) + return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports + + +def _sources_from_virtual_source_port(source_port, _traversed=None): + """ + Recursively resolve source ports over the virtual connections. + Keep track of traversed sources to avoid recursive loops. + """ + _traversed = set(_traversed or []) # a new set! + if source_port in _traversed: + raise LoopError('Loop found when resolving port type') + _traversed.add(source_port) + + block = source_port.parent_block + flow_graph = source_port.parent_flowgraph + + if not isinstance(block, blocks.VirtualSource): + return [source_port] # nothing to resolve, we're done + + stream_id = block.params['stream_id'].value + + # currently the validation does not allow multiple virtual sinks and one virtual source + # but in the future it may... + connected_virtual_sink_blocks = ( + b for b in flow_graph.iter_enabled_blocks() + if isinstance(b, blocks.VirtualSink) and b.params['stream_id'].value == stream_id + ) + source_ports_per_virtual_connection = ( + _sources_from_virtual_sink_port(b.sinks[0], _traversed) # type: list + for b in connected_virtual_sink_blocks + ) + return list(chain(*source_ports_per_virtual_connection)) # concatenate generated lists of ports + + +def downstream_ports(port): + if port.is_source: + return _sinks_from_virtual_source_port(port) + else: + return _sinks_from_virtual_sink_port(port) + + +def _sinks_from_virtual_source_port(source_port, _traversed=None): + """ + Resolve the sink port that is connected to the given virtual source port. + Use the get sink from virtual sink to recursively resolve subsequent ports. + """ + sink_ports_per_virtual_connection = ( + # there can be multiple ports per virtual connection + _sinks_from_virtual_sink_port(c.sink_port, _traversed) # type: list + for c in source_port.connections(enabled=True) + ) + return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports + + +def _sinks_from_virtual_sink_port(sink_port, _traversed=None): + """ + Recursively resolve sink ports over the virtual connections. + Keep track of traversed sinks to avoid recursive loops. + """ + _traversed = set(_traversed or []) # a new set! + if sink_port in _traversed: + raise LoopError('Loop found when resolving port type') + _traversed.add(sink_port) + + block = sink_port.parent_block + flow_graph = sink_port.parent_flowgraph + + if not isinstance(block, blocks.VirtualSink): + return [sink_port] + + stream_id = block.params['stream_id'].value + + connected_virtual_source_blocks = ( + b for b in flow_graph.iter_enabled_blocks() + if isinstance(b, blocks.VirtualSource) and b.params['stream_id'].value == stream_id + ) + sink_ports_per_virtual_connection = ( + _sinks_from_virtual_source_port(b.sources[0], _traversed) # type: list + for b in connected_virtual_source_blocks + ) + return list(chain(*sink_ports_per_virtual_connection)) # concatenate generated lists of ports diff --git a/grc/core/ports/clone.py b/grc/core/ports/clone.py new file mode 100644 index 0000000000..4e1320f81d --- /dev/null +++ b/grc/core/ports/clone.py @@ -0,0 +1,38 @@ +# Copyright 2016 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 .port import Port, Element + + +class PortClone(Port): + + def __init__(self, parent, direction, master, name, key): + Element.__init__(self, parent) + self.master_port = master + + self.name = name + self.key = key + self.multiplicity = 1 + + def __getattr__(self, item): + return getattr(self.master_port, item) + + def add_clone(self): + raise NotImplementedError() + + def remove_clone(self, port): + raise NotImplementedError() diff --git a/grc/core/ports/port.py b/grc/core/ports/port.py new file mode 100644 index 0000000000..139aae0ccc --- /dev/null +++ b/grc/core/ports/port.py @@ -0,0 +1,207 @@ +# Copyright 2008-2016 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 __future__ import absolute_import + +from . import _virtual_connections + +from .. import Constants +from ..base import Element +from ..utils.descriptors import ( + EvaluatedFlag, EvaluatedEnum, EvaluatedPInt, + setup_names, lazy_property +) + + +@setup_names +class Port(Element): + + is_port = True + + dtype = EvaluatedEnum(list(Constants.TYPE_TO_SIZEOF.keys()), default='') + vlen = EvaluatedPInt() + multiplicity = EvaluatedPInt() + hidden = EvaluatedFlag() + optional = EvaluatedFlag() + + def __init__(self, parent, direction, id, label='', domain=Constants.DEFAULT_DOMAIN, dtype='', + vlen='', multiplicity=1, optional=False, hide='', **_): + """Make a new port from nested data.""" + Element.__init__(self, parent) + + self._dir = direction + self.key = id + if not label: + label = id if not id.isdigit() else {'sink': 'in', 'source': 'out'}[direction] + id + self.name = self._base_name = label + + self.domain = domain + self.dtype = dtype + self.vlen = vlen + + if domain == Constants.GR_MESSAGE_DOMAIN: # ToDo: message port class + self.key = self.name + self.dtype = 'message' + + self.multiplicity = multiplicity + self.optional = optional + self.hidden = hide + # end of args ######################################################## + self.clones = [] # References to cloned ports (for nports > 1) + + def __str__(self): + if self.is_source: + return 'Source - {}({})'.format(self.name, self.key) + if self.is_sink: + return 'Sink - {}({})'.format(self.name, self.key) + + def __repr__(self): + return '{!r}.{}[{}]'.format(self.parent, 'sinks' if self.is_sink else 'sources', self.key) + + @property + def item_size(self): + return Constants.TYPE_TO_SIZEOF[self.dtype] * self.vlen + + @lazy_property + def is_sink(self): + return self._dir == 'sink' + + @lazy_property + def is_source(self): + return self._dir == 'source' + + @property + def inherit_type(self): + """always empty for e.g. virtual blocks, may eval to empty for 'Wildcard'""" + return not self.dtype + + def validate(self): + Element.validate(self) + platform = self.parent_platform + + num_connections = len(list(self.connections(enabled=True))) + need_connection = not self.optional and not self.hidden + if need_connection and num_connections == 0: + self.add_error_message('Port is not connected.') + + if self.dtype not in Constants.TYPE_TO_SIZEOF.keys(): + self.add_error_message('Type "{}" is not a possible type.'.format(self.dtype)) + + try: + domain = platform.domains[self.domain] + if self.is_sink and not domain.multi_in and num_connections > 1: + self.add_error_message('Domain "{}" can have only one upstream block' + ''.format(self.domain)) + if self.is_source and not domain.multi_out and num_connections > 1: + self.add_error_message('Domain "{}" can have only one downstream block' + ''.format(self.domain)) + except KeyError: + self.add_error_message('Domain key "{}" is not registered.'.format(self.domain)) + + def rewrite(self): + del self.vlen + del self.multiplicity + del self.hidden + del self.optional + del self.dtype + + if self.inherit_type: + self.resolve_empty_type() + + Element.rewrite(self) + + # Update domain if was deduced from (dynamic) port type + if self.domain == Constants.GR_STREAM_DOMAIN and self.dtype == "message": + self.domain = Constants.GR_MESSAGE_DOMAIN + self.key = self.name + if self.domain == Constants.GR_MESSAGE_DOMAIN and self.dtype != "message": + self.domain = Constants.GR_STREAM_DOMAIN + self.key = '0' # Is rectified in rewrite() + + def resolve_virtual_source(self): + """Only used by Generator after validation is passed""" + return _virtual_connections.upstream_ports(self) + + def resolve_empty_type(self): + def find_port(finder): + try: + return next((p for p in finder(self) if not p.inherit_type), None) + except _virtual_connections.LoopError as error: + self.add_error_message(str(error)) + except (StopIteration, Exception): + pass + + try: + port = find_port(_virtual_connections.upstream_ports) or \ + find_port(_virtual_connections.downstream_ports) + self.set_evaluated('dtype', port.dtype) # we don't want to override the template + self.set_evaluated('vlen', port.vlen) # we don't want to override the template + self.domain = port.domain + except AttributeError: + self.domain = Constants.DEFAULT_DOMAIN + + def add_clone(self): + """ + Create a clone of this (master) port and store a reference in self._clones. + + The new port name (and key for message ports) will have index 1... appended. + If this is the first clone, this (master) port will get a 0 appended to its name (and key) + + Returns: + the cloned port + """ + # Add index to master port name if there are no clones yet + if not self.clones: + self.name = self._base_name + '0' + # Also update key for none stream ports + if not self.key.isdigit(): + self.key = self.name + + name = self._base_name + str(len(self.clones) + 1) + # Dummy value 99999 will be fixed later + key = '99999' if self.key.isdigit() else name + + # Clone + port_factory = self.parent_platform.make_port + port = port_factory(self.parent, direction=self._dir, + name=name, key=key, + master=self, cls_key='clone') + + self.clones.append(port) + return port + + def remove_clone(self, port): + """ + Remove a cloned port (from the list of clones only) + Remove the index 0 of the master port name (and key9 if there are no more clones left + """ + self.clones.remove(port) + # Remove index from master port name if there are no more clones + if not self.clones: + self.name = self._base_name + # Also update key for none stream ports + if not self.key.isdigit(): + self.key = self.name + + def connections(self, enabled=None): + """Iterator over all connections to/from this port + + enabled: None for all, True for enabled only, False for disabled only + """ + for con in self.parent_flowgraph.connections: + if self in con and (enabled is None or enabled == con.enabled): + yield con diff --git a/grc/core/schema_checker/__init__.py b/grc/core/schema_checker/__init__.py new file mode 100644 index 0000000000..e92500ed4a --- /dev/null +++ b/grc/core/schema_checker/__init__.py @@ -0,0 +1,5 @@ +from .validator import Validator + +from .block import BLOCK_SCHEME +from .domain import DOMAIN_SCHEME +from .flow_graph import FLOW_GRAPH_SCHEME diff --git a/grc/core/schema_checker/block.py b/grc/core/schema_checker/block.py new file mode 100644 index 0000000000..db8830fddf --- /dev/null +++ b/grc/core/schema_checker/block.py @@ -0,0 +1,57 @@ +from .utils import Spec, expand, str_ + +PARAM_SCHEME = expand( + base_key=str_, # todo: rename/remove + + id=str_, + label=str_, + category=str_, + + dtype=str_, + default=object, + + options=list, + option_labels=list, + option_attributes=Spec(types=dict, required=False, item_scheme=(str_, list)), + + hide=str_, +) +PORT_SCHEME = expand( + label=str_, + domain=str_, + + id=str_, + dtype=str_, + vlen=(int, str_), + + multiplicity=(int, str_), + optional=(bool, int, str_), + hide=(bool, str_), +) +TEMPLATES_SCHEME = expand( + imports=str_, + var_make=str_, + make=str_, + callbacks=list, +) +BLOCK_SCHEME = expand( + id=Spec(types=str_, required=True, item_scheme=None), + label=str_, + category=(list, str_), + flags=(list, str_), + + parameters=Spec(types=list, required=False, item_scheme=PARAM_SCHEME), + inputs=Spec(types=list, required=False, item_scheme=PORT_SCHEME), + outputs=Spec(types=list, required=False, item_scheme=PORT_SCHEME), + + checks=(list, str_), + value=str_, + + templates=Spec(types=dict, required=False, item_scheme=TEMPLATES_SCHEME), + + documentation=str_, + + file_format=Spec(types=int, required=True, item_scheme=None), + + block_wrapper_path=str_, # todo: rename/remove +) diff --git a/grc/core/schema_checker/domain.py b/grc/core/schema_checker/domain.py new file mode 100644 index 0000000000..86c29ed3c6 --- /dev/null +++ b/grc/core/schema_checker/domain.py @@ -0,0 +1,16 @@ +from .utils import Spec, expand, str_ + +DOMAIN_CONNECTION = expand( + type=Spec(types=list, required=True, item_scheme=None), + connect=str_, +) + +DOMAIN_SCHEME = expand( + id=Spec(types=str_, required=True, item_scheme=None), + label=str_, + color=str_, + multiple_connections_per_input=bool, + multiple_connections_per_output=bool, + + templates=Spec(types=list, required=False, item_scheme=DOMAIN_CONNECTION) +)
\ No newline at end of file diff --git a/grc/core/schema_checker/flow_graph.py b/grc/core/schema_checker/flow_graph.py new file mode 100644 index 0000000000..746fbf4aa7 --- /dev/null +++ b/grc/core/schema_checker/flow_graph.py @@ -0,0 +1,23 @@ +from .utils import Spec, expand, str_ + +OPTIONS_SCHEME = expand( + parameters=Spec(types=dict, required=False, item_scheme=(str_, str_)), + states=Spec(types=dict, required=False, item_scheme=(str_, str_)), +) + +BLOCK_SCHEME = expand( + name=str_, + id=str_, + **OPTIONS_SCHEME +) + +FLOW_GRAPH_SCHEME = expand( + options=Spec(types=dict, required=False, item_scheme=OPTIONS_SCHEME), + blocks=Spec(types=dict, required=False, item_scheme=BLOCK_SCHEME), + connections=list, + + metadata=Spec(types=dict, required=True, item_scheme=expand( + file_format=Spec(types=int, required=True, item_scheme=None), + )) + +) diff --git a/grc/core/schema_checker/utils.py b/grc/core/schema_checker/utils.py new file mode 100644 index 0000000000..a9cf4c0175 --- /dev/null +++ b/grc/core/schema_checker/utils.py @@ -0,0 +1,27 @@ +import collections + +import six + +Spec = collections.namedtuple('Spec', 'types required item_scheme') + + +def expand(**kwargs): + def expand_spec(spec): + if not isinstance(spec, Spec): + types_ = spec if isinstance(spec, tuple) else (spec,) + spec = Spec(types=types_, required=False, item_scheme=None) + elif not isinstance(spec.types, tuple): + spec = Spec(types=(spec.types,), required=spec.required, + item_scheme=spec.item_scheme) + return spec + return {key: expand_spec(value) for key, value in kwargs.items()} + + +str_ = six.string_types + + +class Message(collections.namedtuple('Message', 'path type message')): + fmt = '{path}: {type}: {message}' + + def __str__(self): + return self.fmt.format(**self._asdict()) diff --git a/grc/core/schema_checker/validator.py b/grc/core/schema_checker/validator.py new file mode 100644 index 0000000000..ab4d43bc67 --- /dev/null +++ b/grc/core/schema_checker/validator.py @@ -0,0 +1,102 @@ +# Copyright 2016 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 __future__ import print_function + +import six + +from .utils import Message, Spec + + +class Validator(object): + + def __init__(self, scheme=None): + self._path = [] + self.scheme = scheme + self.messages = [] + self.passed = False + + def run(self, data): + if not self.scheme: + return True + self._reset() + self._path.append('block') + self._check(data, self.scheme) + self._path.pop() + return self.passed + + def _reset(self): + del self.messages[:] + del self._path[:] + self.passed = True + + def _check(self, data, scheme): + if not data or not isinstance(data, dict): + self._error('Empty data or not a dict') + return + if isinstance(scheme, dict): + self._check_dict(data, scheme) + else: + self._check_var_key_dict(data, *scheme) + + def _check_var_key_dict(self, data, key_type, value_scheme): + for key, value in six.iteritems(data): + if not isinstance(key, key_type): + self._error('Key type {!r} for {!r} not in valid types'.format( + type(value).__name__, key)) + if isinstance(value_scheme, Spec): + self._check_dict(value, value_scheme) + elif not isinstance(value, value_scheme): + self._error('Value type {!r} for {!r} not in valid types'.format( + type(value).__name__, key)) + + def _check_dict(self, data, scheme): + for key, (types_, required, item_scheme) in six.iteritems(scheme): + try: + value = data[key] + except KeyError: + if required: + self._error('Missing required entry {!r}'.format(key)) + continue + + self._check_value(value, types_, item_scheme, label=key) + + for key in set(data).difference(scheme): + self._warn('Ignoring extra key {!r}'.format(key)) + + def _check_list(self, data, scheme, label): + for i, item in enumerate(data): + self._path.append('{}[{}]'.format(label, i)) + self._check(item, scheme) + self._path.pop() + + def _check_value(self, value, types_, item_scheme, label): + if not isinstance(value, types_): + self._error('Value type {!r} for {!r} not in valid types'.format( + type(value).__name__, label)) + if item_scheme: + if isinstance(value, list): + self._check_list(value, item_scheme, label) + elif isinstance(value, dict): + self._check(value, item_scheme) + + def _error(self, msg): + self.messages.append(Message('.'.join(self._path), 'error', msg)) + self.passed = False + + def _warn(self, msg): + self.messages.append(Message('.'.join(self._path), 'warn', msg)) diff --git a/grc/core/utils/__init__.py b/grc/core/utils/__init__.py index d095179a10..2d12e280b5 100644 --- a/grc/core/utils/__init__.py +++ b/grc/core/utils/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2008-2015 Free Software Foundation, Inc. +# Copyright 2016 Free Software Foundation, Inc. # This file is part of GNU Radio # # GNU Radio Companion is free software; you can redistribute it and/or @@ -17,8 +17,4 @@ from __future__ import absolute_import -from . import expr_utils -from . import epy_block_io -from . import extract_docs - -from ._complexity import calculate_flowgraph_complexity +from . import epy_block_io, expr_utils, extract_docs, flow_graph_complexity diff --git a/grc/core/utils/backports/__init__.py b/grc/core/utils/backports/__init__.py new file mode 100644 index 0000000000..a24ee3ae01 --- /dev/null +++ b/grc/core/utils/backports/__init__.py @@ -0,0 +1,25 @@ +# Copyright 2016 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. + +from __future__ import absolute_import + +try: + from collections import ChainMap +except ImportError: + from .chainmap import ChainMap diff --git a/grc/core/utils/backports/chainmap.py b/grc/core/utils/backports/chainmap.py new file mode 100644 index 0000000000..1f4f4a96fb --- /dev/null +++ b/grc/core/utils/backports/chainmap.py @@ -0,0 +1,106 @@ +# from https://hg.python.org/cpython/file/default/Lib/collections/__init__.py + +from collections import MutableMapping + + +class ChainMap(MutableMapping): + """ A ChainMap groups multiple dicts (or other mappings) together + to create a single, updateable view. + + The underlying mappings are stored in a list. That list is public and can + be accessed or updated using the *maps* attribute. There is no other + state. + + Lookups search the underlying mappings successively until a key is found. + In contrast, writes, updates, and deletions only operate on the first + mapping. + + """ + + def __init__(self, *maps): + """Initialize a ChainMap by setting *maps* to the given mappings. + If no mappings are provided, a single empty dictionary is used. + + """ + self.maps = list(maps) or [{}] # always at least one map + + def __missing__(self, key): + raise KeyError(key) + + def __getitem__(self, key): + for mapping in self.maps: + try: + return mapping[key] # can't use 'key in mapping' with defaultdict + except KeyError: + pass + return self.__missing__(key) # support subclasses that define __missing__ + + def get(self, key, default=None): + return self[key] if key in self else default + + def __len__(self): + return len(set().union(*self.maps)) # reuses stored hash values if possible + + def __iter__(self): + return iter(set().union(*self.maps)) + + def __contains__(self, key): + return any(key in m for m in self.maps) + + def __bool__(self): + return any(self.maps) + + def __repr__(self): + return '{0.__class__.__name__}({1})'.format( + self, ', '.join(map(repr, self.maps))) + + @classmethod + def fromkeys(cls, iterable, *args): + """Create a ChainMap with a single dict created from the iterable.""" + return cls(dict.fromkeys(iterable, *args)) + + def copy(self): + """New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]""" + return self.__class__(self.maps[0].copy(), *self.maps[1:]) + + __copy__ = copy + + def new_child(self, m=None): # like Django's Context.push() + """New ChainMap with a new map followed by all previous maps. + If no map is provided, an empty dict is used. + """ + if m is None: + m = {} + return self.__class__(m, *self.maps) + + @property + def parents(self): # like Django's Context.pop() + """New ChainMap from maps[1:].""" + return self.__class__(*self.maps[1:]) + + def __setitem__(self, key, value): + self.maps[0][key] = value + + def __delitem__(self, key): + try: + del self.maps[0][key] + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def popitem(self): + """Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.""" + try: + return self.maps[0].popitem() + except KeyError: + raise KeyError('No keys found in the first mapping.') + + def pop(self, key, *args): + """Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].""" + try: + return self.maps[0].pop(key, *args) + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def clear(self): + """Clear maps[0], leaving maps[1:] intact.""" + self.maps[0].clear() diff --git a/grc/core/utils/shlex.py b/grc/core/utils/backports/shlex.py index 6b620fa396..6b620fa396 100644 --- a/grc/core/utils/shlex.py +++ b/grc/core/utils/backports/shlex.py diff --git a/grc/core/utils/descriptors/__init__.py b/grc/core/utils/descriptors/__init__.py new file mode 100644 index 0000000000..80c5259230 --- /dev/null +++ b/grc/core/utils/descriptors/__init__.py @@ -0,0 +1,26 @@ +# Copyright 2016 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 ._lazy import lazy_property, nop_write + +from .evaluated import ( + Evaluated, + EvaluatedEnum, + EvaluatedPInt, + EvaluatedFlag, + setup_names, +) diff --git a/grc/core/utils/descriptors/_lazy.py b/grc/core/utils/descriptors/_lazy.py new file mode 100644 index 0000000000..a0cb126932 --- /dev/null +++ b/grc/core/utils/descriptors/_lazy.py @@ -0,0 +1,39 @@ +# Copyright 2016 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 functools + + +class lazy_property(object): + + def __init__(self, func): + self.func = func + functools.update_wrapper(self, func) + + def __get__(self, instance, owner): + if instance is None: + return self + value = self.func(instance) + setattr(instance, self.func.__name__, value) + return value + + +def nop_write(prop): + """Make this a property with a nop setter""" + def nop(self, value): + pass + return prop.setter(nop) diff --git a/grc/core/utils/descriptors/evaluated.py b/grc/core/utils/descriptors/evaluated.py new file mode 100644 index 0000000000..313cee5b96 --- /dev/null +++ b/grc/core/utils/descriptors/evaluated.py @@ -0,0 +1,112 @@ +# Copyright 2016 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 Evaluated(object): + def __init__(self, expected_type, default, name=None): + self.expected_type = expected_type + self.default = default + + self.name = name or 'evaled_property_{}'.format(id(self)) + self.eval_function = self.default_eval_func + + @property + def name_raw(self): + return '_' + self.name + + def default_eval_func(self, instance): + raw = getattr(instance, self.name_raw) + try: + value = instance.parent_block.evaluate(raw) + except Exception as error: + if raw: + instance.add_error_message("Failed to eval '{}': {}".format(raw, error)) + return self.default + + if not isinstance(value, self.expected_type): + instance.add_error_message("Can not cast evaluated value '{}' to type {}" + "".format(value, self.expected_type)) + return self.default + # print(instance, self.name, raw, value) + return value + + def __call__(self, func): + self.name = func.__name__ + self.eval_function = func + return self + + def __get__(self, instance, owner): + if instance is None: + return self + attribs = instance.__dict__ + try: + value = attribs[self.name] + except KeyError: + value = attribs[self.name] = self.eval_function(instance) + return value + + def __set__(self, instance, value): + attribs = instance.__dict__ + value = value or self.default + if isinstance(value, str) and value.startswith('${') and value.endswith('}'): + attribs[self.name_raw] = value[2:-1].strip() + else: + attribs[self.name] = type(self.default)(value) + + def __delete__(self, instance): + attribs = instance.__dict__ + if self.name_raw in attribs: + attribs.pop(self.name, None) + + +class EvaluatedEnum(Evaluated): + def __init__(self, allowed_values, default=None, name=None): + self.allowed_values = allowed_values if isinstance(allowed_values, (list, tuple)) else \ + allowed_values.split() + default = default if default is not None else self.allowed_values[0] + super(EvaluatedEnum, self).__init__(str, default, name) + + def default_eval_func(self, instance): + value = super(EvaluatedEnum, self).default_eval_func(instance) + if value not in self.allowed_values: + instance.add_error_message("Value '{}' not in allowed values".format(value)) + return self.default + return value + + +class EvaluatedPInt(Evaluated): + def __init__(self, name=None): + super(EvaluatedPInt, self).__init__(int, 1, name) + + def default_eval_func(self, instance): + value = super(EvaluatedPInt, self).default_eval_func(instance) + if value < 1: + # todo: log + return self.default + return value + + +class EvaluatedFlag(Evaluated): + def __init__(self, name=None): + super(EvaluatedFlag, self).__init__((bool, int), False, name) + + +def setup_names(cls): + for name, attrib in cls.__dict__.items(): + if isinstance(attrib, Evaluated): + attrib.name = name + return cls diff --git a/grc/core/utils/expr_utils.py b/grc/core/utils/expr_utils.py index cc03e9cb1c..427585e93c 100644 --- a/grc/core/utils/expr_utils.py +++ b/grc/core/utils/expr_utils.py @@ -23,17 +23,105 @@ import string import six + +def expr_replace(expr, replace_dict): + """ + Search for vars in the expression and add the prepend. + + Args: + expr: an expression string + replace_dict: a dict of find:replace + + Returns: + a new expression with the prepend + """ + expr_splits = _expr_split(expr, var_chars=VAR_CHARS + '.') + for i, es in enumerate(expr_splits): + if es in list(replace_dict.keys()): + expr_splits[i] = replace_dict[es] + return ''.join(expr_splits) + + +def get_variable_dependencies(expr, vars): + """ + Return a set of variables used in this expression. + + Args: + expr: an expression string + vars: a list of variable names + + Returns: + a subset of vars used in the expression + """ + expr_toks = _expr_split(expr) + return set(v for v in vars if v in expr_toks) + + +def sort_objects(objects, get_id, get_expr): + """ + Sort a list of objects according to their expressions. + + Args: + objects: the list of objects to sort + get_id: the function to extract an id from the object + get_expr: the function to extract an expression from the object + + Returns: + a list of sorted objects + """ + id2obj = {get_id(obj): obj for obj in objects} + # Map obj id to expression code + id2expr = {get_id(obj): get_expr(obj) for obj in objects} + # Sort according to dependency + sorted_ids = _sort_variables(id2expr) + # Return list of sorted objects + return [id2obj[id] for id in sorted_ids] + + +import ast + + +def dependencies(expr, names=None): + node = ast.parse(expr, mode='eval') + used_ids = frozenset([n.id for n in ast.walk(node) if isinstance(n, ast.Name)]) + return used_ids & names if names else used_ids + + +def sort_objects2(objects, id_getter, expr_getter, check_circular=True): + known_ids = {id_getter(obj) for obj in objects} + + def dependent_ids(obj): + deps = dependencies(expr_getter(obj)) + return [id_ if id_ in deps else None for id_ in known_ids] + + objects = sorted(objects, key=dependent_ids) + + if check_circular: # walk var defines step by step + defined_ids = set() # variables defined so far + for obj in objects: + deps = dependencies(expr_getter(obj), known_ids) + if not defined_ids.issuperset(deps): # can't have an undefined dep + raise RuntimeError(obj, deps, defined_ids) + defined_ids.add(id_getter(obj)) # define this one + + return objects + + + + VAR_CHARS = string.ascii_letters + string.digits + '_' -class graph(object): +class _graph(object): """ Simple graph structure held in a dictionary. """ - def __init__(self): self._graph = dict() + def __init__(self): + self._graph = dict() - def __str__(self): return str(self._graph) + def __str__(self): + return str(self._graph) def add_node(self, node_key): if node_key in self._graph: @@ -61,7 +149,7 @@ class graph(object): return self._graph[node_key] -def expr_split(expr, var_chars=VAR_CHARS): +def _expr_split(expr, var_chars=VAR_CHARS): """ Split up an expression by non alphanumeric characters, including underscore. Leave strings in-tact. @@ -93,40 +181,7 @@ def expr_split(expr, var_chars=VAR_CHARS): return [t for t in toks if t] -def expr_replace(expr, replace_dict): - """ - Search for vars in the expression and add the prepend. - - Args: - expr: an expression string - replace_dict: a dict of find:replace - - Returns: - a new expression with the prepend - """ - expr_splits = expr_split(expr, var_chars=VAR_CHARS + '.') - for i, es in enumerate(expr_splits): - if es in list(replace_dict.keys()): - expr_splits[i] = replace_dict[es] - return ''.join(expr_splits) - - -def get_variable_dependencies(expr, vars): - """ - Return a set of variables used in this expression. - - Args: - expr: an expression string - vars: a list of variable names - - Returns: - a subset of vars used in the expression - """ - expr_toks = expr_split(expr) - return set(v for v in vars if v in expr_toks) - - -def get_graph(exprs): +def _get_graph(exprs): """ Get a graph representing the variable dependencies @@ -138,7 +193,7 @@ def get_graph(exprs): """ vars = list(exprs.keys()) # Get dependencies for each expression, load into graph - var_graph = graph() + var_graph = _graph() for var in vars: var_graph.add_node(var) for var, expr in six.iteritems(exprs): @@ -148,7 +203,7 @@ def get_graph(exprs): return var_graph -def sort_variables(exprs): +def _sort_variables(exprs): """ Get a list of variables in order of dependencies. @@ -159,7 +214,7 @@ def sort_variables(exprs): a list of variable names @throws Exception circular dependencies """ - var_graph = get_graph(exprs) + var_graph = _get_graph(exprs) sorted_vars = list() # Determine dependency order while var_graph.get_nodes(): @@ -173,24 +228,3 @@ def sort_variables(exprs): for var in indep_vars: var_graph.remove_node(var) return reversed(sorted_vars) - - -def sort_objects(objects, get_id, get_expr): - """ - Sort a list of objects according to their expressions. - - Args: - objects: the list of objects to sort - get_id: the function to extract an id from the object - get_expr: the function to extract an expression from the object - - Returns: - a list of sorted objects - """ - id2obj = dict([(get_id(obj), obj) for obj in objects]) - # Map obj id to expression code - id2expr = dict([(get_id(obj), get_expr(obj)) for obj in objects]) - # Sort according to dependency - sorted_ids = sort_variables(id2expr) - # Return list of sorted objects - return [id2obj[id] for id in sorted_ids] diff --git a/grc/core/utils/extract_docs.py b/grc/core/utils/extract_docs.py index cff8a81099..7688f98de5 100644 --- a/grc/core/utils/extract_docs.py +++ b/grc/core/utils/extract_docs.py @@ -98,8 +98,7 @@ def docstring_from_make(key, imports, make): if '$' in blk_cls: raise ValueError('Not an identifier') ns = dict() - for _import in imports: - exec(_import.strip(), ns) + exec(imports.strip(), ns) blk = eval(blk_cls, ns) doc_strings = {key: blk.__doc__} @@ -166,7 +165,8 @@ class SubprocessLoader(object): else: break # normal termination, return finally: - self._worker.terminate() + if self._worker: + self._worker.terminate() else: print("Warning: docstring loader crashed too often", file=sys.stderr) self._thread = None @@ -277,7 +277,7 @@ elif __name__ == '__main__': print(key) for match, doc in six.iteritems(docs): print('-->', match) - print(doc.strip()) + print(str(doc).strip()) print() print() diff --git a/grc/core/utils/_complexity.py b/grc/core/utils/flow_graph_complexity.py index c0f3ae9de4..d06f04ab5f 100644 --- a/grc/core/utils/_complexity.py +++ b/grc/core/utils/flow_graph_complexity.py @@ -1,5 +1,5 @@ -def calculate_flowgraph_complexity(flowgraph): +def calculate(flowgraph): """ Determines the complexity of a flowgraph """ dbal = 0 for block in flowgraph.blocks: @@ -8,8 +8,8 @@ def calculate_flowgraph_complexity(flowgraph): continue # Don't worry about optional sinks? - sink_list = [c for c in block.sinks if not c.get_optional()] - source_list = [c for c in block.sources if not c.get_optional()] + sink_list = [c for c in block.sinks if not c.optional] + source_list = [c for c in block.sources if not c.optional] sinks = float(len(sink_list)) sources = float(len(source_list)) base = max(min(sinks, sources), 1) @@ -22,8 +22,8 @@ def calculate_flowgraph_complexity(flowgraph): multi = 1 # Connection ratio multiplier - sink_multi = max(float(sum(len(c.get_connections()) for c in sink_list) / max(sinks, 1.0)), 1.0) - source_multi = max(float(sum(len(c.get_connections()) for c in source_list) / max(sources, 1.0)), 1.0) + sink_multi = max(float(sum(len(c.connections()) for c in sink_list) / max(sinks, 1.0)), 1.0) + source_multi = max(float(sum(len(c.connections()) for c in source_list) / max(sources, 1.0)), 1.0) dbal += base * multi * sink_multi * source_multi blocks = float(len(flowgraph.blocks)) diff --git a/grc/gui/Application.py b/grc/gui/Application.py index c1456c3a8d..b4df4d172e 100644 --- a/grc/gui/Application.py +++ b/grc/gui/Application.py @@ -20,18 +20,19 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA from __future__ import absolute_import, print_function +import logging import os import subprocess -import logging from gi.repository import Gtk, Gio, GLib, GObject -from . import Dialogs, Actions, Executor, FileDialogs, Utils, Bars +from . import Constants, Dialogs, Actions, Executor, FileDialogs, Utils, Bars + from .MainWindow import MainWindow -from .ParserErrorsDialog import ParserErrorsDialog +# from .ParserErrorsDialog import ParserErrorsDialog from .PropsDialog import PropsDialog -from ..core import ParseXML, Messages +from ..core import Messages log = logging.getLogger(__name__) @@ -212,9 +213,9 @@ class Application(Gtk.Application): main.update_panel_visibility(main.CONSOLE, Actions.TOGGLE_CONSOLE_WINDOW.get_active()) main.update_panel_visibility(main.VARIABLES, Actions.TOGGLE_FLOW_GRAPH_VAR_EDITOR.get_active()) - if ParseXML.xml_failures: - Messages.send_xml_errors_if_any(ParseXML.xml_failures) - Actions.XML_PARSER_ERRORS_DISPLAY.set_enabled(True) + #if ParseXML.xml_failures: + # Messages.send_xml_errors_if_any(ParseXML.xml_failures) + # Actions.XML_PARSER_ERRORS_DISPLAY.set_enabled(True) # Force an update on the current page to match loaded preferences. # In the future, change the __init__ order to load preferences first @@ -284,11 +285,11 @@ class Application(Gtk.Application): for param in block.params.values(): for variable in flow_graph.get_variables(): # If a block parameter exists that is a variable, create a parameter for it - if param.get_value() == variable.get_id(): + if param.get_value() == variable.name: params.append(param.get_value()) for flow_param in flow_graph.get_parameters(): # If a block parameter exists that is a parameter, create a parameter for it - if param.get_value() == flow_param.get_id(): + if param.get_value() == flow_param.name: params.append(param.get_value()) @@ -302,15 +303,15 @@ class Application(Gtk.Application): for connection in block.connections: # Get id of connected blocks - source_id = connection.source_block.get_id() - sink_id = connection.sink_block.get_id() + source_id = connection.source_block.name + sink_id = connection.sink_block.name # If connected block is not in the list of selected blocks create a pad for it if flow_graph.get_block(source_id) not in flow_graph.selected_blocks(): - pads.append({'key': connection.sink_port.key, 'coord': connection.source_port.coordinate, 'block_id' : block.get_id(), 'direction': 'source'}) + pads.append({'key': connection.sink_port.key, 'coord': connection.source_port.coordinate, 'block_id' : block.name, 'direction': 'source'}) if flow_graph.get_block(sink_id) not in flow_graph.selected_blocks(): - pads.append({'key': connection.source_port.key, 'coord': connection.sink_port.coordinate, 'block_id' : block.get_id(), 'direction': 'sink'}) + pads.append({'key': connection.source_port.key, 'coord': connection.sink_port.coordinate, 'block_id' : block.name, 'direction': 'sink'}) # Copy the selected blocks and paste them into a new page @@ -324,10 +325,10 @@ class Application(Gtk.Application): # Set flow graph to heir block type top_block = flow_graph.get_block("top_block") - top_block.get_param('generate_options').set_value('hb') + top_block.params['generate_options'].set_value('hb') # this needs to be a unique name - top_block.get_param('id').set_value('new_heir') + top_block.params['id'].set_value('new_heir') # Remove the default samp_rate variable block that is created remove_me = flow_graph.get_block("samp_rate") @@ -339,7 +340,7 @@ class Application(Gtk.Application): for param in params: param_id = flow_graph.add_new_block('parameter',(x_pos,10)) param_block = flow_graph.get_block(param_id) - param_block.get_param('id').set_value(param) + param_block.params['id'].set_value(param) x_pos = x_pos + 100 for pad in pads: @@ -357,10 +358,10 @@ class Application(Gtk.Application): source = source_block.get_source(pad['key']) # Ensure the port types match - while pad_sink.get_type() != source.get_type(): + while pad_sink.dtype != source.dtype: # Special case for some blocks that have non-standard type names, e.g. uhd - if pad_sink.get_type() == 'complex' and source.get_type() == 'fc32': + if pad_sink.dtype == 'complex' and source.dtype == 'fc32': break; pad_block.type_controller_modify(1) @@ -378,9 +379,9 @@ class Application(Gtk.Application): sink = sink_block.get_sink(pad['key']) # Ensure the port types match - while sink.get_type() != pad_source.get_type(): + while sink.dtype != pad_source.dtype: # Special case for some blocks that have non-standard type names, e.g. uhd - if pad_source.get_type() == 'complex' and sink.get_type() == 'fc32': + if pad_source.dtype == 'complex' and sink.dtype == 'fc32': break; pad_block.type_controller_modify(1) @@ -567,7 +568,8 @@ class Application(Gtk.Application): # View Parser Errors ################################################## elif action == Actions.XML_PARSER_ERRORS_DISPLAY: - ParserErrorsDialog(ParseXML.xml_failures).run() + # ParserErrorsDialog(ParseXML.xml_failures).run() + pass ################################################## # Undo/Redo ################################################## @@ -616,7 +618,7 @@ class Application(Gtk.Application): #otherwise try to save else: try: - ParseXML.to_file(flow_graph.export_data(), page.file_path) + self.platform.save_flow_graph(page.file_path, flow_graph) flow_graph.grc_file_path = page.file_path page.saved = True except IOError: @@ -639,14 +641,14 @@ class Application(Gtk.Application): else: dup_file_path = page.file_path dup_file_name = '.'.join(dup_file_path.split('.')[:-1]) + "_copy" # Assuming .grc extension at the end of file_path - dup_file_path_temp = dup_file_name+'.grc' + dup_file_path_temp = dup_file_name + Constants.FILE_EXTENSION count = 1 while os.path.exists(dup_file_path_temp): - dup_file_path_temp = dup_file_name+'('+str(count)+').grc' + dup_file_path_temp = '{}({}){}'.format(dup_file_name, count, Constants.FILE_EXTENSION) count += 1 dup_file_path_user = FileDialogs.SaveFlowGraph(main, dup_file_path_temp).run() if dup_file_path_user is not None: - ParseXML.to_file(flow_graph.export_data(), dup_file_path_user) + self.platform.save_flow_graph(dup_file_path_user, flow_graph) Messages.send('Saved Copy to: "' + dup_file_path_user + '"\n') except IOError: Messages.send_fail_save("Can not create a copy of the flowgraph\n") @@ -707,11 +709,13 @@ class Application(Gtk.Application): elif action == Actions.PAGE_CHANGE: # pass and run the global actions pass elif action == Actions.RELOAD_BLOCKS: - self.platform.build_block_library() + self.platform.build_library() main.btwin.repopulate() - Actions.XML_PARSER_ERRORS_DISPLAY.set_enabled(bool( - ParseXML.xml_failures)) - Messages.send_xml_errors_if_any(ParseXML.xml_failures) + + #todo: implement parser error dialog for YAML + #Actions.XML_PARSER_ERRORS_DISPLAY.set_enabled(bool(ParseXML.xml_failures)) + #Messages.send_xml_errors_if_any(ParseXML.xml_failures) + # Force a redraw of the graph, by getting the current state and re-importing it main.update_pages() @@ -721,8 +725,9 @@ class Application(Gtk.Application): main.btwin.search_entry.grab_focus() elif action == Actions.OPEN_HIER: for b in flow_graph.selected_blocks(): - if b._grc_source: - main.new_page(b._grc_source, show=True) + grc_source = b.extra_data.get('grc_source', '') + if grc_source: + main.new_page(b.grc_source, show=True) elif action == Actions.BUSSIFY_SOURCES: for b in flow_graph.selected_blocks(): b.bussify('source') @@ -781,8 +786,8 @@ class Application(Gtk.Application): Actions.BLOCK_CREATE_HIER.set_enabled(bool(selected_blocks)) Actions.OPEN_HIER.set_enabled(bool(selected_blocks)) - Actions.BUSSIFY_SOURCES.set_enabled(bool(selected_blocks)) - Actions.BUSSIFY_SINKS.set_enabled(bool(selected_blocks)) + #Actions.BUSSIFY_SOURCES.set_enabled(bool(selected_blocks)) + #Actions.BUSSIFY_SINKS.set_enabled(bool(selected_blocks)) Actions.RELOAD_BLOCKS.enable() Actions.FIND_BLOCKS.enable() diff --git a/grc/gui/BlockTreeWindow.py b/grc/gui/BlockTreeWindow.py index 8504200459..3b9b8642f9 100644 --- a/grc/gui/BlockTreeWindow.py +++ b/grc/gui/BlockTreeWindow.py @@ -172,7 +172,7 @@ class BlockTreeWindow(Gtk.VBox): # add block iter_ = treestore.insert_before(categories[category], None) treestore.set_value(iter_, KEY_INDEX, block.key) - treestore.set_value(iter_, NAME_INDEX, block.name) + treestore.set_value(iter_, NAME_INDEX, block.label) treestore.set_value(iter_, DOC_INDEX, _format_doc(block.documentation)) def update_docs(self): @@ -225,7 +225,7 @@ class BlockTreeWindow(Gtk.VBox): self.expand_module_in_tree() else: matching_blocks = [b for b in list(self.platform.blocks.values()) - if key in b.key.lower() or key in b.name.lower()] + if key in b.key.lower() or key in b.label.lower()] self.treestore_search.clear() self._categories_search = {tuple(): None} diff --git a/grc/gui/Dialogs.py b/grc/gui/Dialogs.py index 51213a6154..45c9095313 100644 --- a/grc/gui/Dialogs.py +++ b/grc/gui/Dialogs.py @@ -241,15 +241,15 @@ class ErrorsDialog(Gtk.Dialog): self.store.clear() for element, message in flowgraph.iter_error_messages(): if element.is_block: - src, aspect = element.get_id(), '' + src, aspect = element.name, '' elif element.is_connection: - src = element.source_block.get_id() - aspect = "Connection to '{}'".format(element.sink_block.get_id()) + src = element.source_block.name + aspect = "Connection to '{}'".format(element.sink_block.name) elif element.is_port: - src = element.parent_block.get_id() + src = element.parent_block.name aspect = "{} '{}'".format('Sink' if element.is_sink else 'Source', element.name) elif element.is_param: - src = element.parent_block.get_id() + src = element.parent_block.name aspect = "Param '{}'".format(element.name) else: src = aspect = '' diff --git a/grc/gui/MainWindow.py b/grc/gui/MainWindow.py index f913d63966..01502b38f9 100644 --- a/grc/gui/MainWindow.py +++ b/grc/gui/MainWindow.py @@ -249,7 +249,7 @@ class MainWindow(Gtk.ApplicationWindow): return try: #try to load from file if file_path: Messages.send_start_load(file_path) - flow_graph = self._platform.get_new_flow_graph() + flow_graph = self._platform.make_flow_graph() flow_graph.grc_file_path = file_path #print flow_graph page = Page( diff --git a/grc/gui/ParamWidgets.py b/grc/gui/ParamWidgets.py index 71cb1b7a7d..18d1da736b 100644 --- a/grc/gui/ParamWidgets.py +++ b/grc/gui/ParamWidgets.py @@ -88,11 +88,11 @@ class InputParam(Gtk.HBox): Set the markup, color, tooltip, show/hide. """ self.label.set_markup(self.param.format_label_markup(self._have_pending_changes)) - self.set_color('dtype_' + self.param.get_type()) + self.set_color('dtype_' + self.param.dtype) self.set_tooltip_text(self.param.format_tooltip_text()) - if self.param.get_hide() == 'all': + if self.param.hide == 'all': self.hide() else: self.show_all() @@ -214,19 +214,17 @@ class EnumParam(InputParam): def __init__(self, *args, **kwargs): InputParam.__init__(self, *args, **kwargs) self._input = Gtk.ComboBoxText() - for option_name in self.param.options_names: + for option_name in self.param.options.values(): self._input.append_text(option_name) - value = self.param.get_value() - active_index = self.param.options.index(value) - self._input.set_active(active_index) - + self.param_values = list(self.param.options) + self._input.set_active(self.param_values.index(self.param.get_value())) self._input.connect('changed', self._editing_callback) self._input.connect('changed', self._apply_change) self.pack_start(self._input, False, False, 0) def get_text(self): - return self.param.options[self._input.get_active()] + return self.param_values[self._input.get_active()] def set_tooltip_text(self, text): self._input.set_tooltip_text(text) @@ -238,13 +236,13 @@ class EnumEntryParam(InputParam): def __init__(self, *args, **kwargs): InputParam.__init__(self, *args, **kwargs) self._input = Gtk.ComboBoxText.new_with_entry() - for option_name in self.param.options_names: + for option_name in self.param.options.values(): self._input.append_text(option_name) + self.param_values = list(self.param.options) value = self.param.get_value() try: - active_index = self.param.options.index(value) - self._input.set_active(active_index) + self._input.set_active(self.param_values.index(value)) except ValueError: self._input.set_active(-1) self._input.get_child().set_text(value) @@ -263,7 +261,7 @@ class EnumEntryParam(InputParam): if self.has_custom_value: return self._input.get_child().get_text() else: - return self.param.options[self._input.get_active()] + return self.param_values[self._input.get_active()] def set_tooltip_text(self, text): if self.has_custom_value: # custom entry @@ -304,16 +302,16 @@ class FileParam(EntryParam): dirname = os.getcwd() # fix bad paths #build the dialog - if self.param.get_type() == 'file_open': + if self.param.dtype == 'file_open': file_dialog = Gtk.FileChooserDialog('Open a Data File...', None, Gtk.FileChooserAction.OPEN, ('gtk-cancel',Gtk.ResponseType.CANCEL,'gtk-open',Gtk.ResponseType.OK)) - elif self.param.get_type() == 'file_save': + elif self.param.dtype == 'file_save': file_dialog = Gtk.FileChooserDialog('Save a Data File...', None, Gtk.FileChooserAction.SAVE, ('gtk-cancel',Gtk.ResponseType.CANCEL, 'gtk-save',Gtk.ResponseType.OK)) file_dialog.set_do_overwrite_confirmation(True) file_dialog.set_current_name(basename) #show the current filename else: - raise ValueError("Can't open file chooser dialog for type " + repr(self.param.get_type())) + raise ValueError("Can't open file chooser dialog for type " + repr(self.param.dtype)) file_dialog.set_current_folder(dirname) #current directory file_dialog.set_select_multiple(False) file_dialog.set_local_only(True) diff --git a/grc/gui/Platform.py b/grc/gui/Platform.py index aeade75d78..2a38bc619e 100644 --- a/grc/gui/Platform.py +++ b/grc/gui/Platform.py @@ -24,7 +24,8 @@ import os from .Config import Config from . import canvas -from ..core.Platform import Platform as CorePlatform +from ..core.platform import Platform as CorePlatform +from ..core.utils.backports import ChainMap class Platform(CorePlatform): @@ -61,8 +62,15 @@ class Platform(CorePlatform): Config = Config FlowGraph = canvas.FlowGraph Connection = canvas.Connection - block_classes = {key: canvas.Block.make_cls_with_base(cls) - for key, cls in CorePlatform.block_classes.items()} + + def new_block_class(self, block_id, **data): + cls = CorePlatform.new_block_class(self, block_id, **data) + return canvas.Block.make_cls_with_base(cls) + + block_classes_build_in = {key: canvas.Block.make_cls_with_base(cls) + for key, cls in CorePlatform.block_classes_build_in.items()} + block_classes = ChainMap({}, block_classes_build_in) + port_classes = {key: canvas.Port.make_cls_with_base(cls) for key, cls in CorePlatform.port_classes.items()} param_classes = {key: canvas.Param.make_cls_with_base(cls) diff --git a/grc/gui/PropsDialog.py b/grc/gui/PropsDialog.py index 5f39770e78..9ce9bf2701 100644 --- a/grc/gui/PropsDialog.py +++ b/grc/gui/PropsDialog.py @@ -40,7 +40,7 @@ class PropsDialog(Gtk.Dialog): Gtk.Dialog.__init__( self, - title='Properties: ' + block.name, + title='Properties: ' + block.label, transient_for=parent, modal=True, destroy_with_parent=True, @@ -70,7 +70,7 @@ class PropsDialog(Gtk.Dialog): # Params boxes for block parameters self._params_boxes = [] - self._build_param_tab_boxes(block.params) + self._build_param_tab_boxes() # Docs for the block self._docs_text_display = doc_view = SimpleTextDisplay() @@ -109,26 +109,26 @@ class PropsDialog(Gtk.Dialog): self.connect('response', self._handle_response) self.show_all() # show all (performs initial gui update) - def _build_param_tab_boxes(self, params): - tab_labels = (p.tab_label for p in self._block.params.values()) + def _build_param_tab_boxes(self): + categories = (p.category for p in self._block.params.values()) - def unique_tab_labels(): + def unique_categories(): seen = {Constants.DEFAULT_PARAM_TAB} yield Constants.DEFAULT_PARAM_TAB - for tab_label in tab_labels: - if tab_label in seen: + for cat in categories: + if cat in seen: continue - yield tab_label - seen.add(tab_label) + yield cat + seen.add(cat) - for tab in unique_tab_labels(): + for category in unique_categories(): label = Gtk.Label() vbox = Gtk.VBox() scroll_box = Gtk.ScrolledWindow() scroll_box.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scroll_box.add(vbox) self.notebook.append_page(scroll_box, label) - self._params_boxes.append((tab, label, vbox)) + self._params_boxes.append((category, label, vbox)) def _params_changed(self): """ @@ -143,7 +143,7 @@ class PropsDialog(Gtk.Dialog): """ old_hash = self._hash new_hash = self._hash = hash(tuple( - (hash(param), param.name, param.get_type(), param.get_hide() == 'all',) + (hash(param), param.name, param.dtype, param.hide == 'all',) for param in self._block.params.values() )) return new_hash != old_hash @@ -171,7 +171,7 @@ class PropsDialog(Gtk.Dialog): """ if force or self._params_changed(): # hide params box before changing - for tab, label, vbox in self._params_boxes: + for category, label, vbox in self._params_boxes: vbox.hide() # empty the params box for child in vbox.get_children(): @@ -181,7 +181,7 @@ class PropsDialog(Gtk.Dialog): box_all_valid = True for param in self._block.params.values(): # todo: why do we even rebuild instead of really hiding params? - if param.get_tab_label() != tab or param.get_hide() == 'all': + if param.category != category or param.hide == 'all': continue box_all_valid = box_all_valid and param.is_valid() @@ -190,7 +190,7 @@ class PropsDialog(Gtk.Dialog): vbox.pack_start(input_widget, input_widget.expand, True, 1) label.set_markup('<span foreground="{color}">{name}</span>'.format( - color='black' if box_all_valid else 'red', name=Utils.encode(tab) + color='black' if box_all_valid else 'red', name=Utils.encode(category) )) vbox.show() # show params box with new params @@ -225,7 +225,7 @@ class PropsDialog(Gtk.Dialog): buf.insert(pos, '\n') # if given the current parameters an exact match can be made - block_constructor = self._block.get_make().rsplit('.', 2)[-1] + block_constructor = self._block.templates.render('make').rsplit('.', 2)[-1] block_class = block_constructor.partition('(')[0].strip() if block_class in docstrings: docstrings = {block_class: docstrings[block_class]} @@ -246,9 +246,9 @@ class PropsDialog(Gtk.Dialog): key = block.key if key == 'epy_block': - src = block.get_param('_source_code').get_value() + src = block.params['_source_code'].get_value() elif key == 'epy_module': - src = block.get_param('source_code').get_value() + src = block.params['source_code'].get_value() else: src = '' @@ -259,12 +259,12 @@ class PropsDialog(Gtk.Dialog): buf.insert(buf.get_end_iter(), text) buf.delete(buf.get_start_iter(), buf.get_end_iter()) - insert('# Imports\n', '\n'.join(block.get_imports())) + insert('# Imports\n', block.templates.render('imports').strip('\n')) if block.is_variable: - insert('\n\n# Variables\n', block.get_var_make()) - insert('\n\n# Blocks\n', block.get_make()) + insert('\n\n# Variables\n', block.templates.render('var_make')) + insert('\n\n# Blocks\n', block.templates.render('make')) if src: - insert('\n\n# External Code ({}.py)\n'.format(block.get_id()), src) + insert('\n\n# External Code ({}.py)\n'.format(block.name), src) def _handle_key_press(self, widget, event): close_dialog = ( diff --git a/grc/gui/VariableEditor.py b/grc/gui/VariableEditor.py index e310676420..c179c8bc84 100644 --- a/grc/gui/VariableEditor.py +++ b/grc/gui/VariableEditor.py @@ -174,13 +174,13 @@ class VariableEditor(Gtk.VBox): # Block specific values if block: if block.key == 'import': - value = block.get_param('import').get_value() + value = block.params['import'].get_value() elif block.key != "variable": value = "<Open Properties>" sp('editable', False) sp('foreground', '#0D47A1') else: - value = block.get_param('value').get_value() + value = block.params['value'].get_value() # Check if there are errors in the blocks. # Show the block error as a tooltip @@ -192,7 +192,7 @@ class VariableEditor(Gtk.VBox): else: # Evaluate and show the value (if it is a variable) if block.key == "variable": - evaluated = str(block.get_param('value').evaluate()) + evaluated = str(block.params['value'].evaluate()) self.set_tooltip_text(evaluated) # Always set the text value. sp('text', value) @@ -227,21 +227,21 @@ class VariableEditor(Gtk.VBox): imports = self.treestore.append(None, [None, 'Imports']) variables = self.treestore.append(None, [None, 'Variables']) for block in self._imports: - self.treestore.append(imports, [block, block.get_param('id').get_value()]) - for block in sorted(self._variables, key=lambda v: v.get_id()): - self.treestore.append(variables, [block, block.get_param('id').get_value()]) + self.treestore.append(imports, [block, block.params['id'].get_value()]) + for block in sorted(self._variables, key=lambda v: v.name): + self.treestore.append(variables, [block, block.params['id'].get_value()]) def _handle_name_edited_cb(self, cell, path, new_text): block = self.treestore[path][BLOCK_INDEX] - block.get_param('id').set_value(new_text) + block.params['id'].set_value(new_text) Actions.VARIABLE_EDITOR_UPDATE() def _handle_value_edited_cb(self, cell, path, new_text): block = self.treestore[path][BLOCK_INDEX] if block.is_import: - block.get_param('import').set_value(new_text) + block.params['import'].set_value(new_text) else: - block.get_param('value').set_value(new_text) + block.params['value'].set_value(new_text) Actions.VARIABLE_EDITOR_UPDATE() def handle_action(self, item, key, event=None): @@ -258,12 +258,12 @@ class VariableEditor(Gtk.VBox): #Actions.BLOCK_PARAM_MODIFY() pass elif key == self.DELETE_BLOCK: - self.emit('remove_block', self._block.get_id()) + self.emit('remove_block', self._block.name) elif key == self.DELETE_CONFIRM: if self._confirm_delete: # Create a context menu to confirm the delete operation confirmation_menu = Gtk.Menu() - block_id = self._block.get_param('id').get_value().replace("_", "__") + block_id = self._block.params['id'].get_value().replace("_", "__") confirm = Gtk.MenuItem(label="Delete {}".format(block_id)) confirm.connect('activate', self.handle_action, self.DELETE_BLOCK) confirmation_menu.add(confirm) diff --git a/grc/gui/canvas/block.py b/grc/gui/canvas/block.py index d336bc139a..33edf988c2 100644 --- a/grc/gui/canvas/block.py +++ b/grc/gui/canvas/block.py @@ -32,7 +32,7 @@ from ..Constants import ( PORT_BORDER_SEPARATION, BLOCK_FONT, PARAM_FONT ) from ...core import utils -from ...core.Block import Block as CoreBlock +from ...core.blocks import Block as CoreBlock class Block(CoreBlock, Drawable): @@ -45,7 +45,7 @@ class Block(CoreBlock, Drawable): """ super(self.__class__, self).__init__(parent, **n) - self.states.update(_coordinate=(0, 0), _rotation=0) + self.states.update(coordinate=(0, 0), rotation=0) self.width = self.height = 0 Drawable.__init__(self) # needs the states and initial sizes @@ -68,7 +68,7 @@ class Block(CoreBlock, Drawable): Returns: the coordinate tuple (x, y) or (0, 0) if failure """ - return Utils.scale(self.states['_coordinate']) + return Utils.scale(self.states['coordinate']) @coordinate.setter def coordinate(self, coor): @@ -85,7 +85,7 @@ class Block(CoreBlock, Drawable): Utils.align_to_grid(coor[0] + offset_x) - offset_x, Utils.align_to_grid(coor[1] + offset_y) - offset_y ) - self.states['_coordinate'] = coor + self.states['coordinate'] = coor @property def rotation(self): @@ -95,7 +95,7 @@ class Block(CoreBlock, Drawable): Returns: the rotation in degrees or 0 if failure """ - return self.states['_rotation'] + return self.states['rotation'] @rotation.setter def rotation(self, rot): @@ -105,7 +105,7 @@ class Block(CoreBlock, Drawable): Args: rot: the rotation in degrees """ - self.states['_rotation'] = rot + self.states['rotation'] = rot def _update_colors(self): self._bg_color = ( @@ -128,7 +128,8 @@ class Block(CoreBlock, Drawable): self._area = (0, 0, self.height, self.width) self.bounds_from_area(self._area) - bussified = self.current_bus_structure['source'], self.current_bus_structure['sink'] + # bussified = self.current_bus_structure['source'], self.current_bus_structure['sink'] + bussified = False, False for ports, has_busses in zip((self.active_sources, self.active_sinks), bussified): if not ports: continue @@ -160,9 +161,9 @@ class Block(CoreBlock, Drawable): PangoCairo.update_layout(cr, params_layout) title_layout.set_markup( - '<span {foreground} font_desc="{font}"><b>{name}</b></span>'.format( + '<span {foreground} font_desc="{font}"><b>{label}</b></span>'.format( foreground='foreground="red"' if not self.is_valid() else '', font=BLOCK_FONT, - name=Utils.encode(self.name) + label=Utils.encode(self.label) ) ) title_width, title_height = title_layout.get_size() @@ -170,7 +171,7 @@ class Block(CoreBlock, Drawable): # update the params layout if not self.is_dummy_block: markups = [param.format_block_surface_markup() - for param in self.params.values() if param.get_hide() not in ('all', 'part')] + for param in self.params.values() if param.hide not in ('all', 'part')] else: markups = ['<span font_desc="{font}"><b>key: </b>{key}</span>'.format(font=PARAM_FONT, key=self.key)] @@ -200,15 +201,15 @@ class Block(CoreBlock, Drawable): get_min_height_for_ports(self.active_sinks), get_min_height_for_ports(self.active_sources)) - def get_min_height_for_bus_ports(ports): - return 2 * PORT_BORDER_SEPARATION + sum( - port.height + PORT_SPACING for port in ports if port.get_type() == 'bus' - ) - PORT_SPACING - - if self.current_bus_structure['sink']: - height = max(height, get_min_height_for_bus_ports(self.active_sinks)) - if self.current_bus_structure['source']: - height = max(height, get_min_height_for_bus_ports(self.active_sources)) + # def get_min_height_for_bus_ports(ports): + # return 2 * PORT_BORDER_SEPARATION + sum( + # port.height + PORT_SPACING for port in ports if port.dtype == 'bus' + # ) - PORT_SPACING + # + # if self.current_bus_structure['sink']: + # height = max(height, get_min_height_for_bus_ports(self.active_sinks)) + # if self.current_bus_structure['source']: + # height = max(height, get_min_height_for_bus_ports(self.active_sources)) self.width, self.height = width, height = Utils.align_to_grid((width, height)) @@ -237,7 +238,7 @@ class Block(CoreBlock, Drawable): # Show the flow graph complexity on the top block if enabled if Actions.TOGGLE_SHOW_FLOWGRAPH_COMPLEXITY.get_active() and self.key == "options": - complexity = utils.calculate_flowgraph_complexity(self.parent) + complexity = utils.flow_graph_complexity.calculate(self.parent) markups.append( '<span foreground="#444" size="medium" font_desc="{font}">' '<b>Complexity: {num}bal</b></span>'.format(num=Utils.num_to_str(complexity), font=BLOCK_FONT) @@ -344,7 +345,8 @@ class Block(CoreBlock, Drawable): Returns: true for change """ - type_templates = ' '.join(p._type for p in self.get_children()) + type_templates = ' '.join(p._type for p in self.params.values()) + type_templates += ' '.join(p.get_raw('dtype') for p in (self.sinks + self.sources)) type_param = None for key, param in six.iteritems(self.params): if not param.is_enum(): @@ -361,10 +363,10 @@ class Block(CoreBlock, Drawable): # Try to increment the enum by direction try: - keys = list(type_param.options) - old_index = keys.index(type_param.get_value()) - new_index = (old_index + direction + len(keys)) % len(keys) - type_param.set_value(keys[new_index]) + values = list(type_param.options) + old_index = values.index(type_param.get_value()) + new_index = (old_index + direction + len(values)) % len(values) + type_param.set_value(values[new_index]) return True except: return False @@ -381,7 +383,7 @@ class Block(CoreBlock, Drawable): """ changed = False # Concat the nports string from the private nports settings of all ports - nports_str = ' '.join(port._nports for port in self.get_ports()) + nports_str = ' '.join(str(port.get_raw('multiplicity')) for port in self.ports()) # Modify all params whose keys appear in the nports string for key, param in six.iteritems(self.params): if param.is_enum() or param.key not in nports_str: diff --git a/grc/gui/canvas/connection.py b/grc/gui/canvas/connection.py index ff790503ef..56dab45570 100644 --- a/grc/gui/canvas/connection.py +++ b/grc/gui/canvas/connection.py @@ -32,7 +32,7 @@ from ..Constants import ( LINE_SELECT_SENSITIVITY, ) from ...core.Connection import Connection as CoreConnection -from ...core.Element import nop_write +from ...core.utils.descriptors import nop_write class Connection(CoreConnection, Drawable): @@ -93,10 +93,9 @@ class Connection(CoreConnection, Drawable): ] self._current_coordinates = None # triggers _make_path() - def get_domain_color(domain_name): - domain = self.parent_platform.domains.get(domain_name, {}) - color_spec = domain.get('color') - return colors.get_color(color_spec) if color_spec else colors.DEFAULT_DOMAIN_COLOR + def get_domain_color(domain_id): + domain = self.parent_platform.domains.get(domain_id, None) + return colors.get_color(domain.color) if domain else colors.DEFAULT_DOMAIN_COLOR if source.domain == GR_MESSAGE_DOMAIN: self._line_width_factor = 1.0 diff --git a/grc/gui/canvas/flowgraph.py b/grc/gui/canvas/flowgraph.py index 14326fd3f6..394b12cfba 100644 --- a/grc/gui/canvas/flowgraph.py +++ b/grc/gui/canvas/flowgraph.py @@ -81,7 +81,7 @@ class FlowGraph(CoreFlowgraph, Drawable): Returns: a unique id """ - block_ids = set(b.get_id() for b in self.blocks) + block_ids = set(b.name for b in self.blocks) for index in count(): block_id = '{}_{}'.format(base_id, index) if block_id not in block_ids: @@ -89,7 +89,7 @@ class FlowGraph(CoreFlowgraph, Drawable): return block_id def install_external_editor(self, param): - target = (param.parent_block.get_id(), param.key) + target = (param.parent_block.name, param.key) if target in self._external_updaters: editor = self._external_updaters[target] @@ -118,7 +118,7 @@ class FlowGraph(CoreFlowgraph, Drawable): def handle_external_editor_change(self, new_value, target): try: block_id, param_key = target - self.get_block(block_id).get_param(param_key).set_value(new_value) + self.get_block(block_id).params[param_key].set_value(new_value) except (IndexError, ValueError): # block no longer exists self._external_updaters[target].stop() @@ -146,7 +146,7 @@ class FlowGraph(CoreFlowgraph, Drawable): # get the new block block = self.new_block(key) block.coordinate = coor - block.get_param('id').set_value(id) + block.params['id'].set_value(id) Actions.ELEMENT_CREATE() return id @@ -262,18 +262,18 @@ class FlowGraph(CoreFlowgraph, Drawable): except (KeyError, SyntaxError, ValueError): pass if block_key == 'epy_block': - block.get_param('_io_cache').set_value(param_data.pop('_io_cache')) - block.get_param('_source_code').set_value(param_data.pop('_source_code')) + block.params['_io_cache'].set_value(param_data.pop('_io_cache')) + block.params['_source_code'].set_value(param_data.pop('_source_code')) block.rewrite() # this creates the other params for param_key, param_value in six.iteritems(param_data): #setup id parameter if param_key == 'id': old_id2block[param_value] = block #if the block id is not unique, get a new block id - if param_value in (blk.get_id() for blk in self.blocks): + if param_value in (blk.name for blk in self.blocks): param_value = self._get_unique_id(param_value) #set value to key - block.get_param(param_key).set_value(param_value) + block.params[param_key].set_value(param_value) #move block to offset coordinate block.move((x_off, y_off)) #update before creating connections @@ -282,11 +282,9 @@ class FlowGraph(CoreFlowgraph, Drawable): for connection_n in connections_n: source = old_id2block[connection_n.get('source_block_id')].get_source(connection_n.get('source_key')) sink = old_id2block[connection_n.get('sink_block_id')].get_sink(connection_n.get('sink_key')) - self.connect(source, sink) - #set all pasted elements selected - for block in selected: - selected = selected.union(set(block.get_connections())) - self.selected_elements = set(selected) + connection = self.connect(source, sink) + selected.add(connection) + self.selected_elements = selected ########################################################################### # Modify Selected diff --git a/grc/gui/canvas/param.py b/grc/gui/canvas/param.py index b027b7653a..e2c335d9cf 100644 --- a/grc/gui/canvas/param.py +++ b/grc/gui/canvas/param.py @@ -39,20 +39,20 @@ class Param(CoreParam): Returns: gtk input class """ - type_ = self.get_type() - if type_ in ('file_open', 'file_save'): + dtype = self.dtype + if dtype in ('file_open', 'file_save'): input_widget_cls = ParamWidgets.FileParam - elif self.is_enum(): + elif dtype == 'enum': input_widget_cls = ParamWidgets.EnumParam elif self.options: input_widget_cls = ParamWidgets.EnumEntryParam - elif type_ == '_multiline': + elif dtype == '_multiline': input_widget_cls = ParamWidgets.MultiLineEntryParam - elif type_ == '_multiline_python_external': + elif dtype == '_multiline_python_external': input_widget_cls = ParamWidgets.PythonEditorParam else: @@ -64,8 +64,8 @@ class Param(CoreParam): block = self.parent # fixme: using non-public attribute here has_callback = \ - hasattr(block, 'get_callbacks') and \ - any(self.key in callback for callback in block._callbacks) + hasattr(block, 'templates') and \ + any(self.key in callback for callback in block.templates.get('callbacks', '')) return '<span {underline} {foreground} font_desc="Sans 9">{label}</span>'.format( underline='underline="low"' if has_callback else '', @@ -76,7 +76,7 @@ class Param(CoreParam): def format_tooltip_text(self): errors = self.get_error_messages() - tooltip_lines = ['Key: ' + self.key, 'Type: ' + self.get_type()] + tooltip_lines = ['Key: ' + self.key, 'Type: ' + self.dtype] if self.is_valid(): value = str(self.get_evaluated()) if len(value) > 100: @@ -117,7 +117,7 @@ class Param(CoreParam): if not self.is_valid(): return _truncate(value) if value in self.options: - return self.options_names[self.options.index(value)] + return self.options[value] # its name ################################################## # Split up formatting by type @@ -125,7 +125,7 @@ class Param(CoreParam): # Default center truncate truncate = 0 e = self.get_evaluated() - t = self.get_type() + t = self.dtype if isinstance(e, bool): return str(e) elif isinstance(e, Constants.COMPLEX_TYPES): diff --git a/grc/gui/canvas/port.py b/grc/gui/canvas/port.py index b74e4adfcc..2ea35f3dd3 100644 --- a/grc/gui/canvas/port.py +++ b/grc/gui/canvas/port.py @@ -26,8 +26,9 @@ from gi.repository import Gtk, PangoCairo, Pango from . import colors from .drawable import Drawable from .. import Actions, Utils, Constants -from ...core.Element import nop_write -from ...core.Port import Port as CorePort + +from ...core.utils.descriptors import nop_write +from ...core.ports import Port as CorePort class Port(CorePort, Drawable): @@ -75,12 +76,13 @@ class Port(CorePort, Drawable): if not self.parent_block.enabled: self._font_color[-1] = 0.4 color = colors.BLOCK_DISABLED_COLOR + elif self.domain == Constants.GR_MESSAGE_DOMAIN: + color = colors.PORT_TYPE_TO_COLOR.get('message') else: self._font_color[-1] = 1.0 - color = colors.PORT_TYPE_TO_COLOR.get(self.get_type()) or colors.PORT_TYPE_TO_COLOR.get('') - vlen = self.get_vlen() - if vlen > 1: - dark = (0, 0, 30 / 255.0, 50 / 255.0, 70 / 255.0)[min(4, vlen)] + color = colors.PORT_TYPE_TO_COLOR.get(self.dtype) or colors.PORT_TYPE_TO_COLOR.get('') + if self.vlen > 1: + dark = (0, 0, 30 / 255.0, 50 / 255.0, 70 / 255.0)[min(4, self.vlen)] color = tuple(max(c - dark, 0) for c in color) self._bg_color = color self._border_color = tuple(max(c - 0.3, 0) for c in color) @@ -108,7 +110,7 @@ class Port(CorePort, Drawable): if cr: PangoCairo.update_layout(cr, self.label_layout) - if self.domain in (Constants.GR_MESSAGE_DOMAIN, Constants.DEFAULT_DOMAIN): + if self.domain in (Constants.GR_MESSAGE_DOMAIN, Constants.GR_STREAM_DOMAIN): self._line_width_factor = 1.0 else: self._line_width_factor = 2.0 @@ -124,9 +126,9 @@ class Port(CorePort, Drawable): self.width = 2 * Constants.PORT_LABEL_PADDING + label_width / Pango.SCALE self.height = 2 * Constants.PORT_LABEL_PADDING + label_height / Pango.SCALE self._label_layout_offsets = [0, Constants.PORT_LABEL_PADDING] - if self.get_type() == 'bus': - self.height += Constants.PORT_EXTRA_BUS_HEIGHT - self._label_layout_offsets[1] += Constants.PORT_EXTRA_BUS_HEIGHT / 2 + # if self.dtype == 'bus': + # self.height += Constants.PORT_EXTRA_BUS_HEIGHT + # self._label_layout_offsets[1] += Constants.PORT_EXTRA_BUS_HEIGHT / 2 self.height += self.height % 2 # uneven height def draw(self, cr): diff --git a/grc/main.py b/grc/main.py index 4d04608269..2d182c226f 100755 --- a/grc/main.py +++ b/grc/main.py @@ -84,6 +84,7 @@ def main(): prefs=gr.prefs(), install_prefix=gr.prefix() ) + platform.build_library() log.debug("Loading application") app = Application(args.flow_graphs, platform) diff --git a/grc/tests/__init__.py b/grc/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/grc/tests/__init__.py diff --git a/grc/tests/resources/file1.xml b/grc/tests/resources/file1.xml new file mode 100644 index 0000000000..f03288b85d --- /dev/null +++ b/grc/tests/resources/file1.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<!-- +Copyright 2014 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 +--> +<block> + <name>testname</name> + <key>block_key</key> + <make>blocks.complex_to_mag_squared($(vlen))</make> + <param> + <name>Vec Length</name> + <key>vlen</key> + <value>1</value> + <type>int</type> + <tab>test</tab> + </param> + <param> + <name>Vec Length</name> + <key>out_type</key> + <value>complex</value> + <type>string</type> + </param> + <param> + <name>Alpha</name> + <key>a</key> + <value>0</value> + <type>($out_type)</type> + </param> + <check>$vlen > 0</check> + <sink> + <name>in</name> + <type>complex</type> + <vlen>2 * $vlen</vlen> + </sink> + <sink> + <name>in2</name> + <type>message</type> + </sink> + <source> + <name>out</name> + <type>$out_type</type> + <vlen>$vlen</vlen> + </source> +</block> diff --git a/grc/tests/resources/file2.xml b/grc/tests/resources/file2.xml new file mode 100644 index 0000000000..1300c7f5a1 --- /dev/null +++ b/grc/tests/resources/file2.xml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<!-- +Copyright 2014 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 +--> +<!-- +################################################### +## And Const Block: +## all types, 1 output, 1 input & const +################################################### + --> +<block> + <name>And Const</name> + <key>blocks_and_const_xx</key> + <category>test</category> + <throttle>1</throttle> + <import>from gnuradio import blocks</import> + <make>blocks.and_const_$(type.fcn)($const)</make> + <callback>set_k($const)</callback> + <param> + <name>IO Type</name> + <key>type</key> + <type>enum</type> + <option> + <name>Int</name> + <key>int</key> + <opt>fcn:ii</opt> + </option> + <option> + <name>Short</name> + <key>short</key> + <opt>fcn:ss</opt> + </option> + <option> + <name>Byte</name> + <key>byte</key> + <opt>fcn:bb</opt> + </option> + </param> + <param> + <name>Constant</name> + <key>const</key> + <value>0</value> + <type>${type}</type> + <hide>#if $log then 'none' else 'part'#</hide> + </param> + <sink> + <name>in</name> + <type>$type</type> + </sink> + <source> + <name>out</name> + <type>$(type.fcn)</type> + </source> + <doc> +This block creates a variable check box. \ +Leave the label blank to use the variable id as the label. + +A check box selects between two values of similar type. \ +Te values do not necessarily need to be of boolean type. + +The GUI hint can be used to position the widget within the application. \ +The hint is of the form [tab_id@tab_index]: [row, col, row_span, col_span]. \ +Both the tab specification and the grid position are optional. + </doc> +</block> diff --git a/grc/tests/resources/file3.xml b/grc/tests/resources/file3.xml new file mode 100644 index 0000000000..71753badb1 --- /dev/null +++ b/grc/tests/resources/file3.xml @@ -0,0 +1,100 @@ +<?xml version="1.0"?> +<!-- +Copyright 2014 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 +--> +<!-- +################################################### +##Variable Check Box: +## a gui check box form +################################################### + --> +<block> + <name>QT GUI Check Box</name> + <key>variable_qtgui_check_box</key> + <import>from PyQt4 import Qt</import> + <var_make>self.$(id) = $(id) = $value</var_make> + <make>#set $win = '_%s_check_box'%$id +#if not $label() + #set $label = '"%s"'%$id +#end if +$win = Qt.QCheckBox($label) +self._$(id)_choices = {True: $true, False: $false} +self._$(id)_choices_inv = dict((v,k) for k,v in self._$(id)_choices.iteritems()) +self._$(id)_callback = lambda i: Qt.QMetaObject.invokeMethod($(win), "setChecked", Qt.Q_ARG("bool", self._$(id)_choices_inv[i])) +self._$(id)_callback(self.$id) +$(win).stateChanged.connect(lambda i: self.set_$(id)(self._$(id)_choices[bool(i)])) +$(gui_hint()($win))</make> + <callback>self.set_$(id)($value)</callback> + <callback>self._$(id)_callback($id)</callback> + <param> + <name>Label</name> + <key>label</key> + <value></value> + <type>string</type> + <hide>#if $label() then 'none' else 'part'#</hide> + </param> + <param> + <name>Type</name> + <key>type</key> + <value>int</value> + <type>enum</type> + <hide>part</hide> + <option><name>Float</name><key>real</key><opt>conv:float</opt></option> + <option><name>Integer</name><key>int</key><opt>conv:int</opt></option> + <option><name>String</name><key>string</key><opt>conv:str</opt></option> + <option><name>Boolean</name><key>bool</key><opt>conv:bool</opt></option> + <option><name>Any</name><key>raw</key><opt>conv:eval</opt></option> + </param> + <param> + <name>Default Value</name> + <key>value</key> + <value>True</value> + <type>$type</type> + </param> + <param> + <name>True</name> + <key>true</key> + <value>True</value> + <type>$type</type> + </param> + <param> + <name>False</name> + <key>false</key> + <value>False</value> + <type>$type</type> + </param> + <param> + <name>GUI Hint</name> + <key>gui_hint</key> + <value></value> + <type>gui_hint</type> + <hide>part</hide> + </param> + <check>$value in ($true, $false)</check> + <doc> +This block creates a variable check box. \ +Leave the label blank to use the variable id as the label. + +A check box selects between two values of similar type. \ +Te values do not necessarily need to be of boolean type. + +The GUI hint can be used to position the widget within the application. \ +The hint is of the form [tab_id@tab_index]: [row, col, row_span, col_span]. \ +Both the tab specification and the grid position are optional. + </doc> +</block> diff --git a/grc/tests/test_block_flags.py b/grc/tests/test_block_flags.py new file mode 100644 index 0000000000..9eecaf20d7 --- /dev/null +++ b/grc/tests/test_block_flags.py @@ -0,0 +1,26 @@ + +from grc.core.blocks._flags import Flags + + +def test_simple(): + assert 'test' in Flags('_test_') + + +def test_deprecated(): + assert Flags.DEPRECATED == 'deprecated' + assert Flags('this is deprecated').deprecated is True + + +def test_extend(): + f = Flags('a') + f += 'b' + assert isinstance(f, Flags) + f += u'b' + assert isinstance(f, Flags) + f = Flags(u'a') + f += 'b' + assert isinstance(f, Flags) + f += u'b' + assert isinstance(f, Flags) + + assert str(f) == 'abb' diff --git a/grc/tests/test_block_templates.py b/grc/tests/test_block_templates.py new file mode 100644 index 0000000000..df9ab37550 --- /dev/null +++ b/grc/tests/test_block_templates.py @@ -0,0 +1,45 @@ +import pytest + +from grc.core.blocks._templates import MakoTemplates +from grc.core.errors import TemplateError + + +class Block(object): + namespace_templates = {} + + templates = MakoTemplates(None) + + def __init__(self, **kwargs): + self.namespace_templates.update(kwargs) + + +def test_simple(): + t = MakoTemplates(_bind_to=Block(num='123'), test='abc${num}') + assert t['test'] == 'abc${num}' + assert t.render('test') == 'abc123' + assert 'abc${num}' in t._template_cache + + +def test_instance(): + block = Block(num='123') + block.templates['test'] = 'abc${num}' + assert block.templates.render('test') == 'abc123' + assert block.templates is block.__dict__['templates'] + + +def test_list(): + templates = ['abc${num}', '${2 * num}c'] + t = MakoTemplates(_bind_to=Block(num='123'), test=templates) + assert t['test'] == templates + assert t.render('test') == ['abc123', '123123c'] + assert set(templates) == set(t._template_cache.keys()) + + +def test_parse_error(): + with pytest.raises(TemplateError): + MakoTemplates(_bind_to=Block(num='123'), test='abc${num NOT CLOSING').render('test') + + +def test_parse_error2(): + with pytest.raises(TemplateError): + MakoTemplates(_bind_to=Block(num='123'), test='abc${ WRONG_VAR }').render('test') diff --git a/grc/tests/test_cheetah_converter.py b/grc/tests/test_cheetah_converter.py new file mode 100644 index 0000000000..7999955436 --- /dev/null +++ b/grc/tests/test_cheetah_converter.py @@ -0,0 +1,132 @@ +"""""" + +import functools +import grc.converter.cheetah_converter as parser + + +def test_basic(): + c = parser.Converter(names={'abc'}) + for convert in (c.convert_simple, c.convert_hard, c.to_python): + assert 'abc' == convert('$abc') + assert 'abc' == convert('$abc()') + assert 'abc' == convert('$(abc)') + assert 'abc' == convert('$(abc())') + assert 'abc' == convert('${abc}') + assert 'abc' == convert('${abc()}') + + assert c.stats['simple'] == 2 * 6 + assert c.stats['hard'] == 1 * 6 + + +def test_simple(): + convert = parser.Converter(names={'abc': {'def'}}) + assert 'abc' == convert.convert_simple('$abc') + assert 'abc.def' == convert.convert_simple('$abc.def') + assert 'abc.def' == convert.convert_simple('$(abc.def)') + assert 'abc.def' == convert.convert_simple('${abc.def}') + try: + convert.convert_simple('$abc.not_a_sub_key') + except NameError: + assert True + else: + assert False + + +def test_conditional(): + convert = parser.Converter(names={'abc'}) + assert '(asb_asd_ if abc > 0 else __not__)' == convert.convert_inline_conditional( + '#if $abc > 0 then asb_$asd_ else __not__') + + +def test_simple_format_string(): + convert = functools.partial(parser.Converter(names={'abc'}).convert_simple, spec=parser.FormatString) + assert '{abc}' == convert('$abc') + assert '{abc:eval}' == convert('$abc()') + assert '{abc}' == convert('$(abc)') + assert '{abc:eval}' == convert('$(abc())') + assert '{abc}' == convert('${abc}') + assert '{abc:eval}' == convert('${abc()}') + + +def test_hard_format_string(): + names = {'abc': {'ff'}, 'param1': {}, 'param2': {}} + convert = functools.partial(parser.Converter(names).convert_hard, spec=parser.FormatString) + assert 'make_a_cool_block_{abc.ff}({param1}, {param2})' == \ + convert('make_a_cool_block_${abc.ff}($param1, $param2)') + + +converter = parser.Converter(names={'abc'}) +c2p = converter.to_python + + +def test_opts(): + assert 'abc abc abc' == c2p('$abc $(abc) ${abc}') + assert 'abc abc.abc abc' == c2p('$abc $abc.abc ${abc}') + assert 'abc abc[''].abc abc' == c2p('$abc $abc[''].abc() ${abc}') + + +def test_nested(): + assert 'abc(abc) abc + abc abc[abc]' == c2p('$abc($abc) $(abc + $abc) ${abc[$abc]}') + assert '(abc_abc_)' == c2p('(abc_$(abc)_)') + + +def test_nested2(): + class Other(parser.Python): + nested_start = '{' + nested_end = '}' + assert 'abc({abc})' == converter.convert('$abc($abc)', spec=Other) + + +def test_nested3(): + class Other(parser.Python): + start = '{' + end = '}' + assert '{abc(abc)}' == converter.convert('$abc($abc)', spec=Other) + + +def test_with_string(): + assert 'abc "$(abc)" abc' == c2p('$abc "$(abc)" ${abc}') + assert 'abc \'$(abc)\' abc' == c2p('$abc \'$(abc)\' ${abc}') + assert 'abc "\'\'$(abc)" abc' == c2p('$abc "\'\'$(abc)" ${abc}') + + +def test_if(): + result = converter.to_mako(""" + #if $abc > 0 + test + #else if $abc < 0 + test + #else + bla + #end if + """) + + expected = """ + % if abc > 0: + test + % elif abc < 0: + test + % else: + bla + % endif + """ + assert result == expected + + +def test_hash_end(): + result = converter.to_mako('$abc#slurp') + assert result == '${abc}\\' + + +def test_slurp_if(): + result = converter.to_mako(""" + $abc#slurp + #if $abc + """) + + expected = """ + ${abc} + % if abc: + """ + assert result == expected + diff --git a/grc/tests/test_evaled_property.py b/grc/tests/test_evaled_property.py new file mode 100644 index 0000000000..27957cd291 --- /dev/null +++ b/grc/tests/test_evaled_property.py @@ -0,0 +1,104 @@ +import collections +import numbers + +from grc.core.utils.descriptors import Evaluated, EvaluatedEnum, EvaluatedPInt + + +class A(object): + def __init__(self, **kwargs): + self.called = collections.defaultdict(int) + self.errors = [] + self.namespace = kwargs + + def add_error_message(self, msg): + self.errors.append(msg) + + @property + def parent_block(self): + return self + + def evaluate(self, expr): + self.called['evaluate'] += 1 + return eval(expr, self.namespace) + + @Evaluated(int, 1) + def foo(self): + self.called['foo'] += 1 + return eval(self._foo) + + bar = Evaluated(numbers.Real, 1.0, name='bar') + + test = EvaluatedEnum(['a', 'b'], 'a', name='test') + + lala = EvaluatedPInt() + + +def test_fixed_value(): + a = A() + a.foo = 10 + + assert not hasattr(a, '_foo') + assert a.foo == 10 + assert a.called['foo'] == 0 + delattr(a, 'foo') + assert a.foo == 10 + assert a.called['foo'] == 0 + + +def test_evaled(): + a = A() + a.foo = '${ 10 + 1 }' + assert getattr(a, '_foo') == '10 + 1' + assert a.foo == 11 and a.foo == 11 + assert a.called['foo'] == 1 + assert a.called['evaluate'] == 0 + delattr(a, 'foo') + assert a.foo == 11 and a.foo == 11 + assert a.called['foo'] == 2 + assert not a.errors + + +def test_evaled_with_default(): + a = A() + a.bar = '${ 10 + 1 }' + assert getattr(a, '_bar') == '10 + 1' + assert a.bar == 11.0 and type(a.bar) == int + assert a.called['evaluate'] == 1 + assert not a.errors + + +def test_evaled_int_with_default(): + a = A(ll=10) + a.lala = '${ ll * 2 }' + assert a.lala == 20 + a.namespace['ll'] = -10 + assert a.lala == 20 + del a.lala + assert a.lala == 1 + assert not a.errors + + +def test_evaled_enum_fixed_value(): + a = A() + a.test = 'a' + assert not hasattr(a, '_test') + assert a.test == 'a' and type(a.test) == str + assert not a.errors + + +def test_evaled_enum(): + a = A(bla=False) + a.test = '${ "a" if bla else "b" }' + assert a.test == 'b' + a.namespace['bla'] = True + assert a.test == 'b' + del a.test + assert a.test == 'a' + assert not a.errors + + +def test_class_access(): + a = A() + a.foo = '${ meme }' + descriptor = getattr(a.__class__, 'foo') + assert descriptor.name_raw == '_foo' diff --git a/grc/tests/test_expr_utils.py b/grc/tests/test_expr_utils.py new file mode 100644 index 0000000000..4f25477bf1 --- /dev/null +++ b/grc/tests/test_expr_utils.py @@ -0,0 +1,41 @@ +import operator + +import pytest + +from grc.core.utils import expr_utils + +id_getter = operator.itemgetter(0) +expr_getter = operator.itemgetter(1) + + +def test_simple(): + objects = [ + ['c', '2 * a + b'], + ['a', '1'], + ['b', '2 * a + unknown * d'], + ['d', '5'], + ] + + expected = [ + ['a', '1'], + ['d', '5'], + ['b', '2 * a + unknown * d'], + ['c', '2 * a + b'], + ] + + out = expr_utils.sort_objects2(objects, id_getter, expr_getter) + + assert out == expected + + +def test_other(): + test = [ + ['c', '2 * a + b'], + ['a', '1'], + ['b', '2 * c + unknown'], + ] + + expr_utils.sort_objects2(test, id_getter, expr_getter, check_circular=False) + + with pytest.raises(RuntimeError): + expr_utils.sort_objects2(test, id_getter, expr_getter) diff --git a/grc/tests/test_generator.py b/grc/tests/test_generator.py new file mode 100644 index 0000000000..4c79ce4bd3 --- /dev/null +++ b/grc/tests/test_generator.py @@ -0,0 +1,46 @@ +# Copyright 2016 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 os import path +import tempfile + +from grc.core.platform import Platform + + +def test_generator(): + # c&p form compiler code. + # todo: make this independent from installed GR + grc_file = path.join(path.dirname(__file__), 'resources', 'test_compiler.grc') + out_dir = tempfile.gettempdir() + + platform = Platform( + name='GNU Radio Companion Compiler', + prefs=None, + version='0.0.0', + ) + platform.build_library() + + flow_graph = platform.make_flow_graph(grc_file) + flow_graph.rewrite() + flow_graph.validate() + + assert flow_graph.is_valid() + + generator = platform.Generator(flow_graph, path.join(path.dirname(__file__), 'resources')) + generator.write() diff --git a/grc/tests/test_xml_parser.py b/grc/tests/test_xml_parser.py new file mode 100644 index 0000000000..c68b6cdc5a --- /dev/null +++ b/grc/tests/test_xml_parser.py @@ -0,0 +1,39 @@ +# Copyright 2017 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 os import path +import sys + +from grc.converter import flow_graph + + +def test_flow_graph_converter(): + filename = path.join(path.dirname(__file__), 'resources', 'test_compiler.grc') + + data = flow_graph.from_xml(filename) + + flow_graph.dump(data, sys.stdout) + + +def test_flow_graph_converter_with_fp(): + filename = path.join(path.dirname(__file__), 'resources', 'test_compiler.grc') + + with open(filename) as fp: + data = flow_graph.from_xml(fp) + + flow_graph.dump(data, sys.stdout) + diff --git a/grc/tests/test_yaml_checker.py b/grc/tests/test_yaml_checker.py new file mode 100644 index 0000000000..e6b466e511 --- /dev/null +++ b/grc/tests/test_yaml_checker.py @@ -0,0 +1,84 @@ +# Copyright 2016 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 yaml + +from grc.core.schema_checker import Validator, BLOCK_SCHEME + + +BLOCK1 = """ +id: block_key +label: testname + +parameters: +- id: vlen + label: Vec Length + dtype: int + default: 1 +- id: out_type + label: Vec Length + dtype: string + default: complex +- id: a + label: Alpha + dtype: ${ out_type } + default: '0' + +inputs: +- label: in + domain: stream + dtype: complex + vlen: ${ 2 * vlen } +- name: in2 + domain: message + id: in2 + +outputs: +- label: out + domain: stream + dtype: ${ out_type } + vlen: ${ vlen } + +templates: + make: blocks.complex_to_mag_squared(${ vlen }) + +file_format: 1 +""" + + +def test_min(): + checker = Validator(BLOCK_SCHEME) + assert checker.run({'id': 'test', 'file_format': 1}), checker.messages + assert not checker.run({'name': 'test', 'file_format': 1}) + + +def test_extra_keys(): + checker = Validator(BLOCK_SCHEME) + assert checker.run({'id': 'test', 'abcdefg': 'nonsense', 'file_format': 1}) + assert checker.messages == [('block', 'warn', "Ignoring extra key 'abcdefg'")] + + +def test_checker(): + checker = Validator(BLOCK_SCHEME) + data = yaml.load(BLOCK1) + passed = checker.run(data) + if not passed: + print() + for msg in checker.messages: + print(msg) + + assert passed, checker.messages |