[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Commit-gnuradio] [gnuradio] 02/23: grc: move docstring extraction into
From: |
git |
Subject: |
[Commit-gnuradio] [gnuradio] 02/23: grc: move docstring extraction into subprocess |
Date: |
Sat, 28 Nov 2015 21:18:06 +0000 (UTC) |
This is an automated email from the git hooks/post-receive script.
jcorgan pushed a commit to branch master
in repository gnuradio.
commit d15065de35a535eae9448dff60270053f35885bd
Author: Sebastian Koslowski <address@hidden>
Date: Tue Aug 18 16:47:41 2015 +0200
grc: move docstring extraction into subprocess
---
grc/base/Platform.py | 1 +
grc/python/Block.py | 27 ++++--
grc/python/Platform.py | 40 +++++++--
grc/python/extract_docs.py | 201 ++++++++++++++++++++++++++++++++++++++++-----
4 files changed, 233 insertions(+), 36 deletions(-)
diff --git a/grc/base/Platform.py b/grc/base/Platform.py
index a72c21a..db2bb76 100644
--- a/grc/base/Platform.py
+++ b/grc/base/Platform.py
@@ -127,6 +127,7 @@ class Platform(_Element):
else: # store the block
self._blocks[key] = block
self._blocks_n[key] = n
+ return block
def load_category_tree_xml(self, xml_file):
"""Validate and parse category tree file and add it to list"""
diff --git a/grc/python/Block.py b/grc/python/Block.py
index f5f4064..239352d 100644
--- a/grc/python/Block.py
+++ b/grc/python/Block.py
@@ -27,7 +27,7 @@ from .. base.Block import Block as _Block
from .. gui.Block import Block as _GUIBlock
from . FlowGraph import _variable_matcher
-from . import epy_block_io, extract_docs
+from . import epy_block_io
class Block(_Block, _GUIBlock):
@@ -44,7 +44,7 @@ class Block(_Block, _GUIBlock):
block a new block
"""
#grab the data
- self._doc = n.find('doc') or ''
+ self._doc = (n.find('doc') or '').strip('\n').replace('\\\n', '')
self._imports = map(lambda i: i.strip(), n.findall('import'))
self._make = n.find('make')
self._var_make = n.find('var_make')
@@ -184,14 +184,14 @@ class Block(_Block, _GUIBlock):
return changed
def get_doc(self):
- doc = self._doc.strip('\n').replace('\\\n', '')
- #merge custom doc with doxygen docs
- return '\n'.join([doc,
extract_docs.extract(self.get_key())]).strip('\n')
+ platform = self.get_parent().get_parent()
+ extracted_docs = platform.block_docstrings.get(self._key, '')
+ return (self._doc + '\n\n' + extracted_docs).strip()
def get_category(self):
return _Block.get_category(self)
- def get_imports(self):
+ def get_imports(self, raw=False):
"""
Resolve all import statements.
Split each import statement at newlines.
@@ -201,11 +201,20 @@ class Block(_Block, _GUIBlock):
Returns:
a list of import statements
"""
+ if raw:
+ return self._imports
return filter(lambda i: i, sum(map(lambda i:
self.resolve_dependencies(i).split('\n'), self._imports), []))
- def get_make(self): 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_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):
"""
diff --git a/grc/python/Platform.py b/grc/python/Platform.py
index 1497099..5698677 100644
--- a/grc/python/Platform.py
+++ b/grc/python/Platform.py
@@ -24,22 +24,24 @@ from gnuradio import gr
from .. base.Platform import Platform as _Platform
from .. gui.Platform import Platform as _GUIPlatform
-from FlowGraph import FlowGraph as _FlowGraph
-from Connection import Connection as _Connection
-from Block import Block as _Block
-from Port import Port as _Port
-from Param import Param as _Param
-from Generator import Generator
-from Constants import (
+
+from . import extract_docs
+from .FlowGraph import FlowGraph as _FlowGraph
+from .Connection import Connection as _Connection
+from .Block import Block as _Block
+from .Port import Port as _Port
+from .Param import Param as _Param
+from .Generator import Generator
+from .Constants import (
HIER_BLOCKS_LIB_DIR, BLOCK_DTD, DEFAULT_FLOW_GRAPH, BLOCKS_DIRS,
PREFS_FILE, PREFS_FILE_OLD, CORE_TYPES
)
-
COLORS = [(name, color) for name, key, sizeof, color in CORE_TYPES]
class Platform(_Platform, _GUIPlatform):
+
def __init__(self):
"""
Make a platform for gnuradio.
@@ -49,6 +51,17 @@ class Platform(_Platform, _GUIPlatform):
os.mkdir(HIER_BLOCKS_LIB_DIR)
if not os.path.exists(os.path.dirname(PREFS_FILE)):
os.mkdir(os.path.dirname(PREFS_FILE))
+
+ self.block_docstrings = block_docstrings = dict()
+
+ def setter(key, docs):
+ block_docstrings[key] = '\n\n'.join(
+ '--- {0} ---\n{1}\n'.format(b, d.replace('\n\n', '\n'))
+ for b, d in docs.iteritems() if d is not None
+ )
+
+ self._docstring_extractor = extract_docs.SubprocessLoader(setter)
+
# init
_Platform.__init__(
self,
@@ -80,6 +93,17 @@ class Platform(_Platform, _GUIPlatform):
except Exception as e:
print >> sys.stderr, e
+ def load_blocks(self):
+ self._docstring_extractor.start()
+ _Platform.load_blocks(self)
+ self._docstring_extractor.finish()
+ self._docstring_extractor.wait()
+
+ def load_block_xml(self, xml_file):
+ block = _Platform.load_block_xml(self, xml_file)
+ self._docstring_extractor.query(block.get_key())
+ return block
+
##############################################
# Constructors
##############################################
diff --git a/grc/python/extract_docs.py b/grc/python/extract_docs.py
index 1124459..837d26c 100644
--- a/grc/python/extract_docs.py
+++ b/grc/python/extract_docs.py
@@ -17,10 +17,20 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
"""
+import sys
import re
+import subprocess
+import threading
+import json
+import Queue
+import random
import itertools
+###############################################################################
+# The docstring extraction
+###############################################################################
+
def docstring_guess_from_key(key):
"""Extract the documentation from the python __doc__ strings
@@ -67,28 +77,181 @@ def docstring_guess_from_key(key):
return doc_strings
-_docs_cache = dict()
+###############################################################################
+# Manage docstring extraction in separate process
+###############################################################################
+class SubprocessLoader(object):
+ """Start and manage docstring extraction process
-def extract(key):
+ Manages subprocess and handles RPC.
"""
- Call the private extract and cache the result.
+ BOOTSTRAP = "import runpy; runpy.run_path({!r}, run_name='__worker__')"
+ AUTH_CODE = random.random() # sort out unwanted output of worker process
+ RESTART = 5 # number of worker restarts before giving up
+ DONE = object() # sentinel value to signal end-of-queue
- Args:
- key: the block key
+ def __init__(self, callback_query_result, callback_finished=None):
+ self.callback_query_result = callback_query_result
+ self.callback_finished = callback_finished or (lambda: None)
- Returns:
- a string with documentation
+ self._queue = Queue.Queue()
+ self._thread = None
+ self._worker = None
+ self._shutdown = threading.Event()
+ self._last_cmd = None
+
+ def start(self):
+ """Start the worker process handler thread"""
+ if self._thread is not None:
+ return
+ self._shutdown.clear()
+ thread = self._thread = threading.Thread(target=self.run_worker)
+ thread.daemon = True
+ thread.start()
+
+ def run_worker(self):
+ """Read docstring back from worker stdout and execute callback."""
+ for _ in range(self.RESTART):
+ if self._shutdown.is_set():
+ break
+ try:
+ self._worker = subprocess.Popen(
+ args=(sys.executable, '-uc',
self.BOOTSTRAP.format(__file__)),
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE
+ )
+ self._handle_worker()
+
+ except (OSError, IOError):
+ msg = "Warning: restarting the docstring loader"
+ cmd, args = self._last_cmd
+ if cmd == 'query':
+ msg += " (crashed while loading {0!r})".format(args[0])
+ print >> sys.stderr, msg
+ continue # restart
+ else:
+ break # normal termination, return
+ finally:
+ self._worker.terminate()
+ else:
+ print >> sys.stderr, "Warning: docstring loader crashed too often"
+ self._thread = None
+ self._worker = None
+ self.callback_finished()
+
+ def _handle_worker(self):
+ """Send commands and responses back from worker."""
+ assert '1' == self._worker.stdout.read(1)
+ for cmd, args in iter(self._queue.get, self.DONE):
+ self._last_cmd = cmd, args
+ self._send(cmd, args)
+ cmd, args = self._receive()
+ self._handle_response(cmd, args)
+
+ def _send(self, cmd, args):
+ """send a command to worker's stdin"""
+ fd = self._worker.stdin
+ json.dump((self.AUTH_CODE, cmd, args), fd)
+ fd.write('\n'.encode())
+
+ def _receive(self):
+ """receive response from worker's stdout"""
+ for line in iter(self._worker.stdout.readline, ''):
+ try:
+ key, cmd, args = json.loads(line, encoding='utf-8')
+ if key != self.AUTH_CODE:
+ raise ValueError('Got wrong auth code')
+ return cmd, args
+ except ValueError:
+ continue # ignore invalid output from worker
+ else:
+ raise IOError("Can't read worker response")
+
+ def _handle_response(self, cmd, args):
+ """Handle response from worker, call the callback"""
+ if cmd == 'result':
+ key, docs = args
+ self.callback_query_result(key, docs)
+ elif cmd == 'error':
+ print args
+ else:
+ print >> sys.stderr, "Unknown response:", cmd, args
+
+ def query(self, key):
+ """request docstring extraction for a certain key"""
+ if self._thread is None:
+ self.start()
+ self._queue.put(('query', (key,)))
+
+ def finish(self):
+ """signal end of requests"""
+ self._queue.put(self.DONE)
+
+ def wait(self):
+ """Wait for the handler thread to die"""
+ if self._thread:
+ self._thread.join()
+
+ def terminate(self):
+ """Terminate the worker and wait"""
+ self._shutdown.set()
+ try:
+ self._worker.terminate()
+ self.wait()
+ except (OSError, AttributeError):
+ pass
+
+
+###############################################################################
+# Main worker entry point
+###############################################################################
+
+def worker_main():
+ """Main entry point for the docstring extraction process.
+
+ Manages RPC with main process through.
+ Runs a docstring extraction for each key it read on stdin.
"""
- if not _docs_cache.has_key(key):
- docstrings = docstring_guess_from_key(key)
- _docs_cache[key] = '\n\n'.join(
- ' --- {0} --- \n\n{1}'.format(match, docstring)
- for match, docstring in docstrings.iteritems()
- )
- return _docs_cache[key]
-
-
-if __name__ == '__main__':
- import sys
- print extract(sys.argv[1])
+ def send(cmd, args):
+ json.dump((code, cmd, args), sys.stdout)
+ sys.stdout.write('\n'.encode())
+
+ sys.stdout.write('1')
+ for line in iter(sys.stdin.readline, ''):
+ code, cmd, args = json.loads(line, encoding='utf-8')
+ try:
+ if cmd == 'query':
+ key, = args
+ send('result', (key, docstring_guess_from_key(key)))
+ elif cmd == 'exit':
+ break
+ except Exception as e:
+ send('error', repr(e))
+
+
+if __name__ == '__worker__':
+ worker_main()
+
+elif __name__ == '__main__':
+ def callback(key, docs):
+ print key
+ for match, doc in docs.iteritems():
+ print '-->', match
+ print doc.strip()
+ print
+ print
+
+ r = SubprocessLoader(callback)
+
+ # r.query('analog_feedforward_agc_cc')
+ # r.query('uhd_source')
+ r.query('expr_utils_graph')
+ r.query('blocks_add_cc')
+ # r.query('analog_feedforward_agc_cc')
+ # r.query('uhd_source')
+ # r.query('uhd_source')
+ # r.query('analog_feedforward_agc_cc')
+ r.finish()
+ # r.terminate()
+ r.wait()
- [Commit-gnuradio] [gnuradio] 01/23: grc: rewrite docstring extraction and allow for underscore in module name, (continued)
- [Commit-gnuradio] [gnuradio] 01/23: grc: rewrite docstring extraction and allow for underscore in module name, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 04/23: grc: try to load block class from <import> and <make> to extract docstring, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 03/23: grc: finish doc_string extraction in the background (faster start-up), git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 18/23: Merge branch 'maint', git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 11/23: gr-dtv: Fix Coverity issue #1327838., git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 10/23: gr-dtv: Fix Coverity issue #1327858., git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 20/23: Merge remote-tracking branch 'jdemel/maint', git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 14/23: digital: clarify gmsk doc, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 15/23: grc: fix Action to str method, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 22/23: Merge remote-tracking branch 'gnuradio-wg-grc/master_grcwg', git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 02/23: grc: move docstring extraction into subprocess,
git <=
- [Commit-gnuradio] [gnuradio] 17/23: grc: preserve block spacing when dragging multiple blocks into canvas boundary, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 16/23: grc: Added option to use the default editor when opening embedded python blocks, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 19/23: Merge remote-tracking branch 'drmpeg/dtv-coverity-clean', git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 12/23: grc: added recently opened flowgraph submenu and toolbar button dropdown menu, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 06/23: grc: auto-generate missing hier_blocks, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 08/23: gr-dtv: Fix Coverity issue #1327535., git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 09/23: gr-dtv: Fix Coverity issue #1327847., git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 13/23: ctrlport: pc_throughput_avg registered, git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 23/23: Merge branch 'maint', git, 2015/11/28
- [Commit-gnuradio] [gnuradio] 05/23: grc: refactoring Messages.py, git, 2015/11/28