[gjs/esm/static-imports: 9/10] Add additional library bindings.




commit 0c84db0c68d21ba65b610b942fcdfc156d7b6086
Author: Evan Welsh <noreply evanwelsh com>
Date:   Sun Nov 8 13:03:04 2020 -0600

    Add additional library bindings.

 js.gresource.xml                     |   5 ++
 lib/modules/esm.js                   |   9 ++-
 lib/modules/gi.js                    |   4 +-
 modules/core/_text.js                |  13 ++++
 modules/core/overrides/GLib.js       |   5 ++
 modules/esm/cairo.js                 |   7 +++
 modules/esm/format.js                |   5 ++
 modules/esm/gettext.js               |  16 +++++
 modules/esm/gi.js                    |  66 ++++++++++++++++++++
 modules/esm/signals.js               | 118 +++++++++++++++++++++++++++++++++++
 modules/esm/system.js                |   2 +
 modules/script/_bootstrap/default.js |  13 ++++
 12 files changed, 256 insertions(+), 7 deletions(-)
---
diff --git a/js.gresource.xml b/js.gresource.xml
index c3a29d94..bbd0106d 100644
--- a/js.gresource.xml
+++ b/js.gresource.xml
@@ -9,8 +9,12 @@
     <file>lib/entry.js</file>
 
     <!-- ESM-based modules -->
+    <file>modules/esm/cairo.js</file>
     <file>modules/esm/gi.js</file>
     <file>modules/esm/system.js</file>
+    <file>modules/esm/format.js</file>
+    <file>modules/esm/signals.js</file>
+    <file>modules/esm/gettext.js</file>
 
     <!-- Script-based Modules -->
     <file>modules/script/_bootstrap/debugger.js</file>
@@ -44,5 +48,6 @@
     <file>modules/core/_format.js</file>
     <file>modules/core/_gettext.js</file>
     <file>modules/core/_signals.js</file>
+    <file>modules/core/_text.js</file>
   </gresource>
 </gresources>
diff --git a/lib/modules/esm.js b/lib/modules/esm.js
index b2e4e3f6..7b7d2676 100644
--- a/lib/modules/esm.js
+++ b/lib/modules/esm.js
@@ -176,10 +176,9 @@ moduleLoader.registerScheme('gi', {
     load(uri) {
         const version = uri.query.version ?? getGIVersionMap(uri.host);
 
-        if (!version)
-            throw new ImportError(`No version specified for ${uri.host}.`);
-
-        giVersionMap.set(uri.host, version);
+        if (version) {
+            giVersionMap.set(uri.host, version);
+        }
 
         return [generateModule(uri.host, version), true];
     },
@@ -209,7 +208,7 @@ setModuleLoadHook(moduleGlobalThis, (id, uri) => {
 
     const registry = getRegistry(moduleGlobalThis);
 
-    registry.set(uri, compiled);
+    registry.set(id, compiled);
 
     return compiled;
 });
diff --git a/lib/modules/gi.js b/lib/modules/gi.js
index fb0076dc..d38bd347 100644
--- a/lib/modules/gi.js
+++ b/lib/modules/gi.js
@@ -5,7 +5,7 @@
  * Creates a module source text to expose a GI namespace via a default export.
  *
  * @param {string} namespace the GI namespace to import
- * @param {string} version the version string of the namespace
+ * @param {string} [version] the version string of the namespace
  *
  * @returns {string} the generated module source text
  */
@@ -13,7 +13,7 @@ export function generateModule(namespace, version) {
     const source = `
     import $$gi from 'gi';
     
-    const $$ns = $$gi.require('${namespace}', '${version}');
+    const $$ns = $$gi.require${version ? `('${namespace}', '${version}')` : `('${namespace}')`};
 
     export default $$ns;
     `;
diff --git a/modules/core/_text.js b/modules/core/_text.js
new file mode 100644
index 00000000..60d081d3
--- /dev/null
+++ b/modules/core/_text.js
@@ -0,0 +1,13 @@
+const ByteArray = imports.byteArray;
+
+var TextDecoder = class TextDecoder {
+    decode(bytes) {
+        return ByteArray.toString(bytes);
+    }
+}
+
+var TextEncoder = class TextEncoder {
+    encode(string) {
+        return ByteArray.fromString(string);
+    }
+}
\ No newline at end of file
diff --git a/modules/core/overrides/GLib.js b/modules/core/overrides/GLib.js
index 20ae7e04..8d17f790 100644
--- a/modules/core/overrides/GLib.js
+++ b/modules/core/overrides/GLib.js
@@ -166,6 +166,11 @@ function _packVariant(signature, value) {
     }
 }
 
+/**
+ * @param {*} variant 
+ * @param {boolean} deep 
+ * @param {boolean} [recursive] 
+ */
 function _unpackVariant(variant, deep, recursive = false) {
     switch (String.fromCharCode(variant.classify())) {
     case 'b':
diff --git a/modules/esm/cairo.js b/modules/esm/cairo.js
new file mode 100644
index 00000000..642f2ccc
--- /dev/null
+++ b/modules/esm/cairo.js
@@ -0,0 +1,7 @@
+const cairo = import.meta.importSync('cairoNative');
+
+export default Object.assign(
+    {},
+    imports._cairo,
+    cairo
+);
\ No newline at end of file
diff --git a/modules/esm/format.js b/modules/esm/format.js
new file mode 100644
index 00000000..1d5dc4c9
--- /dev/null
+++ b/modules/esm/format.js
@@ -0,0 +1,5 @@
+export const vprintf = imports._format.vprintf;
+
+export function format(...args) {
+    return vprintf(this, args);
+}
\ No newline at end of file
diff --git a/modules/esm/gettext.js b/modules/esm/gettext.js
new file mode 100644
index 00000000..29392d05
--- /dev/null
+++ b/modules/esm/gettext.js
@@ -0,0 +1,16 @@
+export let setlocale = imports._gettext.setlocale;
+
+export let textdomain = imports._gettext.textdomain;
+export let bindtextdomain = imports._gettext.bindtextdomain;
+
+export let gettext = imports._gettext.gettext;
+export let dgettext = imports._gettext.dgettext;
+export let dcgettext = imports._gettext.dcgettext;
+
+export let ngettext = imports._gettext.ngettext;
+export let dngettext= imports._gettext.dngettext;
+
+export let pgettext = imports._gettext.pgettext;
+export let dpgettext = imports._gettext.dpgettext;
+
+export let domain = imports._gettext.domain;
diff --git a/modules/esm/gi.js b/modules/esm/gi.js
index f9cbc374..8ee1ef14 100644
--- a/modules/esm/gi.js
+++ b/modules/esm/gi.js
@@ -1,5 +1,7 @@
 const gi = import.meta.importSync('gi');
 
+import System from 'system';
+
 const Gi = {
     require(name, version = null) {
         if (version !== null)
@@ -11,6 +13,70 @@ const Gi = {
 
         return gi[name];
     },
+    requireSymbol(lib, ver, symbol) {
+        if (!checkSymbol(lib, ver, symbol)) {
+            if (symbol)
+                printerr(`Unsatisfied dependency: No ${symbol} in ${lib}`);
+            else
+                printerr(`Unsatisfied dependency: ${lib}`);
+            System.exit(1);
+        }
+    },
+
+    /**
+     * Check whether an external GI typelib can be imported
+     * and provides @symbol.
+     *
+     * Symbols may refer to
+     *  - global functions         ('main_quit')
+     *  - classes                  ('Window')
+     *  - class / instance methods ('IconTheme.get_default' / 'IconTheme.has_icon')
+     *  - GObject properties       ('Window.default_height')
+     *
+     * @param {string} lib an external dependency to import
+     * @param {string} [ver] version of the dependency
+     * @param {string} [symbol] symbol to check for
+     * @returns {boolean} true if `lib` can be imported and provides `symbol`, false
+     * otherwise
+     */
+    checkSymbol(lib, ver, symbol) {
+        let Lib = null;
+
+        if (ver)
+            gi.versions[lib] = ver;
+
+        try {
+            Lib = gi[lib];
+        } catch (e) {
+            return false;
+        }
+
+        if (!symbol)
+            return true; // Done
+
+        let [klass, sym] = symbol.split('.');
+        if (klass === symbol) // global symbol
+            return typeof Lib[symbol] !== 'undefined';
+
+        let obj = Lib[klass];
+        if (typeof obj === 'undefined')
+            return false;
+
+        if (typeof obj[sym] !== 'undefined' ||
+            obj.prototype && typeof obj.prototype[sym] !== 'undefined')
+            return true; // class- or object method
+
+        // GObject property
+        let pspec = null;
+        if (GObject.type_is_a(obj.$gtype, GObject.TYPE_INTERFACE)) {
+            let iface = GObject.type_default_interface_ref(obj.$gtype);
+            pspec = GObject.Object.interface_find_property(iface, sym);
+        } else if (GObject.type_is_a(obj.$gtype, GObject.TYPE_OBJECT)) {
+            pspec = GObject.Object.find_property.call(obj.$gtype, sym);
+        }
+
+        return pspec !== null;
+    }
 };
 Object.freeze(Gi);
 
diff --git a/modules/esm/signals.js b/modules/esm/signals.js
new file mode 100644
index 00000000..d997eda7
--- /dev/null
+++ b/modules/esm/signals.js
@@ -0,0 +1,118 @@
+
+export class EventEmitter {
+    constructor() {
+        this._events = new Map();
+        this._nextConnectionId = 1n;
+    }
+
+    _connectListener(eventName, listener) {
+        const listeners = this._events.get(eventName);
+        if (listeners) {
+            listeners.push(listener);
+        } else {
+            this._events.set(eventName, [listener]);
+        }
+    }
+
+    connect(name, callback) {
+        // be paranoid about callback arg since we'd start to throw from emit()
+        // if it was messed up
+        if (typeof callback !== 'function')
+            throw new Error('When connecting signal must give a callback that is a function');
+
+        // we instantiate the "signal machinery" only on-demand if anything
+        // gets connected.
+
+        let id = this._nextConnectionId;
+        this._nextConnectionId += 1n;
+
+        // this makes it O(n) in total connections to emit, but I think
+        // it's right to optimize for low memory and reentrancy-safety
+        // rather than speed
+
+        this._connectListener(name, {
+            id,
+            name,
+            callback,
+            'disconnected': false,
+        });
+
+        return id;
+    }
+
+    disconnect(id) {
+        this._events.forEach(_events => {
+            for (let i = 0; i < _events.length; ++i) {
+                let connection = _events[i];
+                if (connection.id === id) {
+                    if (connection.disconnected)
+                        throw new Error(`Signal handler id ${id} already disconnected`);
+
+                    // set a flag to deal with removal during emission
+                    connection.disconnected = true;
+                    _events.splice(i, 1);
+
+                    return;
+                }
+            }
+        });
+    }
+
+    signalHandlerIsConnected(id) {
+        this._events.forEach(_events => {
+            for (let i = 0; i < _events.length; ++i) {
+                const connection = this._events[i];
+                if (connection.id === id)
+                    return !connection.disconnected;
+            }
+        });
+
+        return false;
+    }
+
+    disconnectAll() {
+        this._events.forEach(_events => {
+            while (_events.length > 0)
+                this.disconnect.call(this, _events[0].id);
+        });
+    }
+
+    emit(name, ...args) {
+        // may not be any signal handlers at all, if not then return
+        if (!('_events' in this))
+            return;
+
+        // To deal with re-entrancy (removal/addition while
+        // emitting), we copy out a list of what was connected
+        // at emission start; and just before invoking each
+        // handler we check its disconnected flag.
+        let handlers = [...(this._events.get(name) || [])];
+
+        // create arg array which is emitter + everything passed in except
+        // signal name. Would be more convenient not to pass emitter to
+        // the callback, but trying to be 100% consistent with GObject
+        // which does pass it in. Also if we pass in the emitter here,
+        // people don't create closures with the emitter in them,
+        // which would be a cycle.
+        let argArray = [this, ...args];
+
+        for (let i = 0; i < handlers.length; ++i) {
+            let connection = handlers[i];
+            if (!connection.disconnected) {
+                try {
+                    // since we pass "null" for this, the global object will be used.
+                    let ret = connection.callback.apply(null, argArray);
+
+                    // if the callback returns true, we don't call the next
+                    // signal handlers
+                    if (ret === true)
+                        break;
+                } catch (e) {
+                    // just log any exceptions so that callbacks can't disrupt
+                    // signal emission
+                    logError(e, `Exception in callback for signal: ${name}`);
+                }
+            }
+        }
+    }
+}
diff --git a/modules/esm/system.js b/modules/esm/system.js
index cecaa4d7..52c47f8e 100644
--- a/modules/esm/system.js
+++ b/modules/esm/system.js
@@ -15,3 +15,5 @@ export let exit = system.exit;
 export let version = system.version;
 
 export let programInvocationName = system.programInvocationName;
+
+export let clearDateCaches = system.clearDateCaches;
\ No newline at end of file
diff --git a/modules/script/_bootstrap/default.js b/modules/script/_bootstrap/default.js
index eb042d49..e0c1090c 100644
--- a/modules/script/_bootstrap/default.js
+++ b/modules/script/_bootstrap/default.js
@@ -4,8 +4,21 @@
     'use strict';
 
     const {print, printerr, log, logError} = imports._print;
+    const {TextDecoder, TextEncoder} = imports._text;
 
     Object.defineProperties(exports, {
+        TextDecoder: {
+            configurable: false,
+            enumerable: true,
+            writable: false,
+            value: TextDecoder,
+        },
+        TextEncoder: {
+            configurable: false,
+            enumerable: true,
+            writable: false,
+            value: TextEncoder,
+        },
         print: {
             configurable: false,
             enumerable: true,


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