[gnome-code-assistance] [backends/pycommon] Reorganize architecture



commit 850d90e66a2e5ed5be793eae61856f8bf6d04ed4
Author: Jesse van den Kieboom <jessevdk gmail com>
Date:   Sun Nov 10 15:24:51 2013 +0100

    [backends/pycommon] Reorganize architecture

 .../gnome/codeassistance/transport_dbus.py         |  308 ++++++++++++++------
 backends/pycommon/gnome/codeassistance/types.py    |   19 +-
 2 files changed, 231 insertions(+), 96 deletions(-)
---
diff --git a/backends/pycommon/gnome/codeassistance/transport_dbus.py 
b/backends/pycommon/gnome/codeassistance/transport_dbus.py
index 8d75966..3f9433d 100644
--- a/backends/pycommon/gnome/codeassistance/transport_dbus.py
+++ b/backends/pycommon/gnome/codeassistance/transport_dbus.py
@@ -23,148 +23,220 @@ import inspect, sys, os
 from gnome.codeassistance import types
 
 class Document(dbus.service.Object):
+    """Base Document interface.
+
+    Implementations should inherit from this base class which implements the
+    org.gnome.CodeAssist.Document interface.
+    """
+
     interface = 'org.gnome.CodeAssist.Document'
 
+    def __init__(self):
+        super(Document, self).__init__()
+
+        self.id = 0
+        self.path = ''
+        self.client_path = ''
+        self.data_path = ''
+        self.cursor = 0
+
 class Diagnostics(dbus.service.Object):
+    """Diagnostics interface.
+
+    Implementations can inherit from this class to implement the
+    org.gnome.CodeAssist.Diagnostics interface. Diagnostics are served from
+    the .diagnostics field which should be set to a list of types.Diagnostic
+    objects.
+    """
+
     interface = 'org.gnome.CodeAssist.Diagnostics'
 
-    def diagnostics(self):
-        return []
+    def __init__(self):
+        super(Diagnostics, self).__init__()
+        self.diagnostics = []
 
     @dbus.service.method(interface,
                          in_signature='', out_signature='a(ua((x(xx)(xx))s)a(x(xx)(xx))s)')
     def Diagnostics(self):
-        return [d.to_tuple() for d in self.diagnostics()]
-
-DocumentInterfaces = [Document, Diagnostics]
+        return [d.to_tuple() for d in self.diagnostics]
 
 class Service:
     language = None
-    services = []
-
-    def __init__(self, id, name, document):
-        self.id = id
-        self.document = document
-
-    def data_path(self, path, unsaved):
-        for u in unsaved:
-            if u.path == path:
-                return u.data_path
-
-        return path
-
-class Server(dbus.service.Object):
-    apps = {}
-    nextid = 0
 
+    def parse(self, doc, options):
+        """parse a single document.
+
+        parse should be implemented to parse the file located at @path
+        into the provided @doc. @data_path contains the path of the actual data
+        needed to be parsed. If the document is in an unmodified state, then
+        @data_path will be equal to @path. However, if the document is being
+        edited, then @data_path will be a temporary file containing the modified
+        document. @cursor is the current location of the cursor in the document
+        being edited. The @cursor can be used to gather autocompletion
+        information. Finally @options contains backend specific options provided
+        by a client.
+
+        @doc is an object of the register document type and should be populated
+        by the implementation.
+        """
+        pass
+
+    def dispose(self, doc):
+        pass
+
+class Project:
+    def parse_all(self, doc, docs, options):
+        """parse multiple documents.
+
+        parse_all potentially parses multiple documents at the same time.
+        This can be implemented by backends which parse multiple documents at
+        the same time to complete a parse. This is useful for example for
+        parsers that can provide semantic diagnostics based on types of a
+        complete unit instead of only providing syntactic analysis. Examples of
+        languages that should support this are C, Vala or Go (i.e. languages
+        with static typing).
+
+        doc: the primary document needing to be parsed. This is the document
+        requesting analysis and can be used as the starting point for
+        analysis.
+
+        docs: a list of documents which the client is interested in (i.e.
+        these are usually the documents open in the client). An implementation
+        can provide information for the subset of these docs that were analysed
+        in the process of analysing doc. Note that docs always includes at
+        least doc.
+
+        options: an implementation specific set of options passed by the client
+
+        Implementations should do the following steps:
+          1) Determine all the documents belonging to the project of doc
+          2) Parse and analyse these documents in the context of doc
+          3) Gather and supply information to the intersection between documents
+             in docs and the project documents that were processed.
+          4) Return the subset of documents which have newly processed information
+
+        """
+        pass
+
+class Server(object):
     class App:
-        id = 0
-        name = ''
+        def __init__(self):
+            self.id = 0
+            self.name = ''
 
-        docs = {}
-        ids = {}
-        nextid = 0
+            self.docs = {}
+            self.ids = {}
+            self.nextid = 0
 
     def __init__(self, bus, path, service, document):
-        dbus.service.Object.__init__(self, bus, path)
+        super(Server, self).__init__()
+
+        self.apps = {}
+        self.nextid = 0
+
         self.service = service
         self.document = document
 
+        # Export dummy document for introspection purposes
+        self.dummy = self.document()
+        self.dummy.add_to_connection(bus, path + '/document')
+
         bus.add_signal_receiver(self.on_name_lost,
                                 signal_name='NameOwnerChanged',
                                 dbus_interface='org.freedesktop.DBus',
                                 path='/org/freedesktop/DBus')
 
     def on_name_lost(self, name, oldowner, newowner):
-        if newowner == '' and oldowner in self.apps:
+        if newowner != '':
+            return
+
+        try:
             app = self.apps[oldowner]
-            self.dispose(app)
+        except KeyError:
+            return
 
-            if len(self.apps) == 0:
-                GLib.idle_add(self.do_exit)
+        self.dispose_app(app)
 
-    def do_exit(self):
-        sys.exit(0)
+    def make_app(self, appid):
+        app = Server.App()
 
-    def app(self, appid):
-        if not appid in self.apps:
-            app = Server.App()
+        app.id = self.nextid
+        app.name = appid
+        app.service = self.service()
 
-            app.id = self.nextid
-            app.name = appid
-            app.service = self.service(app.id, app.name, self.document)
+        self.apps[appid] = app
+        self.nextid += 1
 
-            self.apps[appid] = app
-            self.nextid += 1
+        return app
 
-            return app
-        else:
+    def ensure_app(self, appid):
+        try:
             return self.apps[appid]
+        except KeyError:
+            return self.make_app(appid)
 
-    @dbus.service.method('org.gnome.CodeAssist.Service',
-                         in_signature='', out_signature='as',
-                         sender_keyword='sender')
-    def SupportedServices(self, sender=None):
-        app = self.app(sender)
-        ret = []
+    def make_document(self, app, path, client_path):
+        doc = self.document()
 
-        bases = inspect.getmro(self.document)
+        doc.id = app.nextid
+        doc.client_path = client_path
+        doc.path = path
 
-        for i in DocumentInterfaces:
-            if i in bases:
-                ret.append(i.interface)
+        app.nextid += 1
+        app.docs[path] = doc
 
-        ret += self.service.services
-        return ret
+        objpath = self._object_path + '/' + str(app.id) + '/documents/' + str(doc.id)
+        doc.add_to_connection(self._connection, objpath)
 
-    @dbus.service.method('org.gnome.CodeAssist.Service',
-                         in_signature='sxa(ss)a{sv}', out_signature='o',
-                         sender_keyword='sender')
-    def Parse(self, path, cursor, unsaved, options, sender=None):
-        path = os.path.normpath(path)
+        return doc
 
-        app = self.app(sender)
-        doc = None
+    def ensure_document(self, app, path, data_path, cursor=0):
+        npath = (path and os.path.normpath(path))
 
-        if path in app.ids:
-            doc = app.docs[app.ids[path]]
+        try:
+            doc = app.docs[npath]
+        except KeyError:
+            doc = self.make_document(app, npath, path)
 
-        unsaved = [types.UnsavedDocument(os.path.normpath(u[0]), os.path.normpath(u[1])) for u in unsaved]
+        doc.data_path = (data_path or path)
+        doc.cursor = cursor
 
-        doc = app.service.parse(path, cursor, unsaved, options, doc)
+        return doc
 
-        if not path in app.ids:
-            doc.add_to_connection(self._connection, self._object_path + '/' + str(app.id) + '/documents/' + 
str(app.nextid))
+    def dispose(self, app, path):
+        try:
+            doc = app.docs[path]
+        except KeyError:
+            return
 
-            app.ids[path] = app.nextid
-            app.docs[app.nextid] = doc
+        self.dispose_document(app, doc)
+        del app.docs[path]
 
-            app.nextid += 1
+    def dispose_document(self, app, doc):
+        app.service.dispose(doc)
+        doc.remove_from_connection()
 
-        return doc._object_path
-
-    def dispose(self, app, path=None):
-        if path is None:
-            path = list(app.ids)
-        else:
-            if path in app.ids:
-                path = [path]
-            else:
-                return
+    def dispose_app(self, app):
+        for doc in app.docs:
+            self.dispose_document(app, app.docs[doc])
 
-        for p in path:
-            id = app.ids[p]
-            doc = app.docs[id]
+        if len(app.docs) == 0:
+            del self.apps[app.name]
 
-            app.service.dispose(doc)
+            if len(self.apps) == 0:
+                GLib.idle_add(lambda: sys.exit(0))
 
-            doc.remove_from_connection()
+class ServeService(dbus.service.Object):
+    @dbus.service.method('org.gnome.CodeAssist.Service',
+                         in_signature='sxa(ss)a{sv}', out_signature='o',
+                         sender_keyword='sender')
+    def Parse(self, path, cursor, data_path, options, sender=None):
+        app = self.ensure_app(sender)
+        doc = self.ensure_document(app, path, data_path, cursor)
 
-            del app.docs[id]
-            del app.ids[p]
+        app.service.parse(doc, options)
 
-        if len(app.ids) == 0:
-            del self.apps[app.name]
+        return doc._object_path
 
     @dbus.service.method('org.gnome.CodeAssist.Service',
                          in_signature='s', out_signature='',
@@ -172,21 +244,69 @@ class Server(dbus.service.Object):
     def Dispose(self, path, sender=None):
         path = os.path.normpath(path)
 
-        if sender in self.apps:
+        try:
             app = self.apps[sender]
-            self.dispose(app, path)
+        except KeyError:
+            return
+
+        self.dispose(app, path)
+
+class ServeProject(dbus.service.Object):
+    @dbus.service.method('org.gnome.CodeAssist.Project',
+                         in_signature='sxa(ss)a{sv}', out_signature='a(so)',
+                         sender_keyword='sender')
+    def ParseAll(self, path, cursor, documents, options, sender=None):
+        app = self.ensure_app(sender)
+        doc = self.ensure_document(app, path, '', cursor)
+
+        opendocs = [types.OpenDocument.from_tuple(d) for d in documents]
+        docs = [self.ensure_document(app, d.path, d.data_path) for d in opendocs]
+
+        parsed = app.service.parse_all(doc, docs, options)
+
+        return [types.RemoteDocument(d.client_path, d._object_path).to_tuple() for d in parsed]
 
 class Transport():
-    def __init__(self, service, document):
+    def __init__(self, service, document, srvtype=Server):
         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
         name = 'org.gnome.CodeAssist.' + service.language
         path = '/org/gnome/CodeAssist/' + service.language
 
         bus = dbus.SessionBus()
+        servercls = self.make_server_cls(service)
 
         self.name = dbus.service.BusName(name, bus)
-        self.server = Server(bus, path, service, document)
+        self.server = servercls(bus, path, service, document)
+
+    def make_server_cls(self, service):
+        types = {
+            Service: ServeService,
+            Project: ServeProject
+        }
+
+        bases = inspect.getmro(service)[1:]
+        sb = []
+
+        for b in bases:
+            try:
+                sb.append(types[b])
+            except KeyError:
+                pass
+
+        if not ServeService in sb:
+            raise ValueError("service should at least inherit from transport.Service")
+
+        sb.append(Server)
+
+        def TheServerInit(self, bus, path, service, document):
+            for b in sb:
+                if b == Server:
+                    b.__init__(self, bus, path, service, document)
+                else:
+                    b.__init__(self, bus, path)
+
+        return type('TheServerType', tuple(sb), {'__init__': TheServerInit})
 
     def run(self):
         ml = GLib.MainLoop()
diff --git a/backends/pycommon/gnome/codeassistance/types.py b/backends/pycommon/gnome/codeassistance/types.py
index c9747bd..7798ee8 100644
--- a/backends/pycommon/gnome/codeassistance/types.py
+++ b/backends/pycommon/gnome/codeassistance/types.py
@@ -15,13 +15,28 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
-class UnsavedDocument:
+class OpenDocument:
     def __init__(self, path='', data_path=''):
         self.path = path
         self.data_path = data_path
 
+    @staticmethod
+    def from_tuple(self, tp):
+        return OpenDocument(tp[0], tp[1])
+
     def __repr__(self):
-        return '<UnsavedDocument: {0}, {1}>'.format(self.path, self.data_path)
+        return '<OpenDocument: {0}, {1}>'.format(self.path, self.data_path)
+
+class RemoteDocument:
+    def __init__(self, path='', remote_path=''):
+        self.path = path
+        self.remote_path = remote_path
+
+    def __repr__(self):
+        return '<RemoteDocument: {0}, {1}>'.format(self.path, self.remote_path)
+
+    def to_tuple(self):
+        return (self.path, self.remote_path)
 
 class SourceLocation:
     def __init__(self, line=0, column=0):


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]