]> git.saurik.com Git - cycript.git/commitdiff
Support requiring simple modules installed by npm.
authorJay Freeman (saurik) <saurik@saurik.com>
Mon, 21 Dec 2015 14:14:20 +0000 (06:14 -0800)
committerJay Freeman (saurik) <saurik@saurik.com>
Mon, 21 Dec 2015 14:14:20 +0000 (06:14 -0800)
.gitignore
Execute.cpp
libcycript.cy

index 637968d6fd6ef2d9695e2a9ee7250e9612b2da62..cabf90ae90c01db9b8f9e4614e23e2f9f1031309 100644 (file)
@@ -46,3 +46,4 @@ Cycript.osx
 Cycript.lib
 DerivedCoreProperties.txt
 PropList.txt
+node_modules
index c6812a2ef4b0d4666d3863531813a75fa30cf110..e575f94c8207b48674a6bdd5920be4095a185610 100644 (file)
@@ -2042,63 +2042,62 @@ static const char *CYPoolLibraryPath(CYPool &pool) {
     return lib;
 }
 
-static JSValueRef require(JSContextRef context, JSObjectRef object, JSObjectRef _this, size_t count, const JSValueRef arguments[], JSValueRef *exception) { CYTry {
+static JSValueRef require_callAsFunction(JSContextRef context, JSObjectRef object, JSObjectRef _this, size_t count, const JSValueRef arguments[], JSValueRef *exception) { CYTry {
     _assert(count == 1);
     CYPool pool;
 
-    const char *lib(CYPoolLibraryPath(pool));
-
-    CYJSString property("exports");
-    JSObjectRef module;
-
     const char *name(CYPoolCString(pool, context, arguments[0]));
-    const char *path(pool.strcat(lib, "/cycript0.9/", name, ".cy", NULL));
-
-    CYJSString key(path);
-    JSObjectRef modules(CYGetCachedObject(context, CYJSString("modules")));
-    JSValueRef cache(CYGetProperty(context, modules, key));
-
-    if (!JSValueIsUndefined(context, cache))
-        module = CYCastJSObject(context, cache);
-    else {
-        CYUTF8String code(CYPoolFileUTF8String(pool, path));
-
-        if (code.data == NULL) {
-            if (strchr(name, '/') == NULL && (
+    if (strchr(name, '/') == NULL && (
 #ifdef __APPLE__
-                dlopen(pool.strcat("/System/Library/Frameworks/", name, ".framework/", name, NULL), RTLD_LAZY | RTLD_GLOBAL) != NULL ||
-                dlopen(pool.strcat("/System/Library/PrivateFrameworks/", name, ".framework/", name, NULL), RTLD_LAZY | RTLD_GLOBAL) != NULL ||
+        dlopen(pool.strcat("/System/Library/Frameworks/", name, ".framework/", name, NULL), RTLD_LAZY | RTLD_GLOBAL) != NULL ||
+        dlopen(pool.strcat("/System/Library/PrivateFrameworks/", name, ".framework/", name, NULL), RTLD_LAZY | RTLD_GLOBAL) != NULL ||
 #endif
-            false))
-                return CYJSUndefined(NULL);
-
-            CYThrow("Can't find module: %s", name);
-        }
+    false))
+        return CYJSUndefined(context);
 
-        module = JSObjectMake(context, NULL, NULL);
-        CYSetProperty(context, modules, key, module);
+    JSObjectRef resolve(CYCastJSObject(context, CYGetProperty(context, object, CYJSString("resolve"))));
+    CYJSString path(context, CYCallAsFunction(context, resolve, NULL, 1, arguments));
 
-        JSObjectRef exports(JSObjectMake(context, NULL, NULL));
-        CYSetProperty(context, module, property, exports);
-
-        std::stringstream wrap;
-        wrap << "(function (exports, require, module) { " << code << "\n});";
-        code = CYPoolCode(pool, *wrap.rdbuf());
+    CYJSString property("exports");
 
-        JSValueRef value(_jsccall(JSEvaluateScript, context, CYJSString(code), NULL, NULL, 0));
-        JSObjectRef function(CYCastJSObject(context, value));
+    JSObjectRef modules(CYGetCachedObject(context, CYJSString("modules")));
+    JSValueRef cache(CYGetProperty(context, modules, path));
 
-        JSValueRef arguments[3] = { exports, JSObjectMakeFunctionWithCallback(context, CYJSString("require"), &require), module };
-        CYCallAsFunction(context, function, NULL, 3, arguments);
+    JSValueRef result;
+    if (!JSValueIsUndefined(context, cache)) {
+        JSObjectRef module(CYCastJSObject(context, cache));
+        result = CYGetProperty(context, module, property);
+    } else {
+        CYUTF8String code(CYPoolFileUTF8String(pool, CYPoolCString(pool, context, path)));
+        _assert(code.data != NULL);
+
+        size_t length(strlen(name));
+        if (length >= 5 && strcmp(name + length - 5, ".json") == 0) {
+            JSObjectRef JSON(CYGetCachedObject(context, CYJSString("JSON")));
+            JSObjectRef parse(CYCastJSObject(context, CYGetProperty(context, JSON, CYJSString("parse"))));
+            JSValueRef arguments[1] = { CYCastJSValue(context, CYJSString(code)) };
+            result = CYCallAsFunction(context, parse, JSON, 1, arguments);
+        } else {
+            JSObjectRef module(JSObjectMake(context, NULL, NULL));
+            CYSetProperty(context, modules, path, module);
+
+            JSObjectRef exports(JSObjectMake(context, NULL, NULL));
+            CYSetProperty(context, module, property, exports);
+
+            std::stringstream wrap;
+            wrap << "(function (exports, require, module, __filename) { " << code << "\n});";
+            code = CYPoolCode(pool, *wrap.rdbuf());
+
+            JSValueRef value(_jsccall(JSEvaluateScript, context, CYJSString(code), NULL, NULL, 0));
+            JSObjectRef function(CYCastJSObject(context, value));
+
+            JSValueRef arguments[4] = { exports, object, module, CYCastJSValue(context, path) };
+            CYCallAsFunction(context, function, NULL, 4, arguments);
+            result = CYGetProperty(context, module, property);
+        }
     }
 
-    JSObjectRef exports(CYCastJSObject(context, CYGetProperty(context, module, property)));
-
-    CYJSString _default("default");
-    if (JSValueIsUndefined(context, CYGetProperty(context, exports, _default)))
-        CYSetProperty(context, exports, _default, exports, kJSPropertyAttributeDontEnum);
-
-    return exports;
+    return result;
 } CYCatch(NULL) }
 
 static bool CYRunScript(JSGlobalContextRef context, const char *path) {
@@ -2146,6 +2145,9 @@ extern "C" void CYSetupContext(JSGlobalContextRef context) {
     JSObjectRef Function_prototype(CYCastJSObject(context, CYGetProperty(context, Function, prototype_s)));
     CYSetProperty(context, cy, CYJSString("Function_prototype"), Function_prototype);
 
+    JSObjectRef JSON(CYCastJSObject(context, CYGetProperty(context, global, CYJSString("JSON"))));
+    CYSetProperty(context, cy, CYJSString("JSON"), JSON);
+
     JSObjectRef Number(CYCastJSObject(context, CYGetProperty(context, global, CYJSString("Number"))));
     CYSetProperty(context, cy, CYJSString("Number"), Number);
 
@@ -2214,13 +2216,14 @@ extern "C" void CYSetupContext(JSGlobalContextRef context) {
     JSObjectRef System(JSObjectMake(context, NULL, NULL));
     CYSetProperty(context, cy, CYJSString("System"), System);
 
-    CYSetProperty(context, all, CYJSString("require"), &require, kJSPropertyAttributeDontEnum);
+    CYSetProperty(context, all, CYJSString("require"), &require_callAsFunction, kJSPropertyAttributeDontEnum);
 
     CYSetProperty(context, global, CYJSString("system"), System);
     CYSetProperty(context, System, CYJSString("args"), CYJSNull(context));
-    //CYSetProperty(context, System, CYJSString("global"), global);
     CYSetProperty(context, System, CYJSString("print"), &System_print);
 
+    CYSetProperty(context, global, CYJSString("global"), global);
+
 #ifdef __APPLE__
     if (&JSWeakObjectMapCreate != NULL) {
         JSWeakObjectMapRef weak(JSWeakObjectMapCreate(context, NULL, &CYDestroyWeak));
index e9a612f04549da3156eb4d65b182c82ff74ab371..6a751aa38ddd2a2ebc7c52e1bda835d67c612d87 100644 (file)
@@ -1,3 +1,9 @@
+var process = {
+    env: {},
+};
+
+(function() {
+
 let $cy_set = function(object, properties) {
     for (const name in properties)
         Object.defineProperty(object, name, {
@@ -8,6 +14,17 @@ let $cy_set = function(object, properties) {
         });
 };
 
+const F_OK = 0;
+const X_OK = (1<<0);
+const W_OK = (1<<1);
+const R_OK = (1<<2);
+
+typedef long size_t;
+
+extern "C" int access(const char *path, int amode);
+extern "C" char *getcwd(char *buf, size_t size);
+extern "C" int getpid();
+
 $cy_set(Date.prototype, {
     toCYON: function() {
         return `new ${this.constructor.name}(${this.toUTCString().toCYON()})`;
@@ -19,3 +36,177 @@ $cy_set(Error.prototype, {
         return `new ${this.constructor.name}(${this.message.toCYON()})`;
     },
 });
+
+let IsFile = function(path) {
+    // XXX: this doesn't work on symlinks, but I don't want to fix stat :/
+    return access(path, F_OK) == 0 && access(path + '/', F_OK) == -1;
+};
+
+let StartsWith = function(lhs, rhs) {
+    return lhs.substring(0, rhs.length) == rhs;
+};
+
+let ResolveFile = function(exact, name) {
+    if (exact && IsFile(name))
+        return name;
+    for (let suffix of ['.js', '.json'])
+        if (IsFile(name + suffix))
+            return name + suffix;
+    return null;
+};
+
+
+let GetLibraryPath = function() {
+    let handle = dlopen("/usr/lib/libcycript.dylib", RTLD_NOLOAD);
+    if (handle == null)
+        return null;
+
+    try {
+        let CYHandleServer = dlsym(handle, "CYHandleServer");
+        if (CYHandleServer == null)
+            return null;
+
+        let info = new Dl_info;
+        if (dladdr(CYHandleServer, info) == 0)
+            return null;
+
+        let path = info->dli_fname;
+        let slash = path.lastIndexOf('/');
+        if (slash == -1)
+            return null;
+
+        return path.substr(0, slash);
+    } finally {
+        dlclose(handle);
+    }
+};
+
+let ResolveFolder = function(name) {
+    if (access(name + '/', F_OK) == -1)
+        return null;
+
+    if (IsFile(name + "/package.json")) {
+        let package = require(name + "/package.json");
+        let path = ResolveFile(true, name + "/" + package.main);
+        if (path != null)
+            return path;
+    }
+
+    return ResolveFile(false, name + "/index");
+};
+
+let ResolveEither = function(name) {
+    let path = null;
+    if (path == null)
+        path = ResolveFile(true, name);
+    if (path == null)
+        path = ResolveFolder(name);
+    return path;
+};
+
+require.resolve = function(name) {
+    if (StartsWith(name, '/')) {
+        let path = ResolveEither(name);
+        if (path != null)
+            return path;
+    } else {
+        let cwd = new (typedef char[1024]);
+        cwd = getcwd(cwd, cwd.length).toString();
+        cwd = cwd.split('/');
+
+        if (StartsWith(name, './') || StartsWith(name, '../')) {
+            let path = ResolveEither(cwd + '/' + name);
+            if (path != null)
+                return path;
+        } else {
+            for (let i = cwd.length; i != 0; --i) {
+                let modules = cwd.slice(0, i).concat("node_modules").join('/');
+                let path = ResolveEither(modules + "/" + name);
+                if (path != null)
+                    return path;
+            }
+
+            let library = GetLibraryPath();
+            let path = ResolveFile(true, library + "/cycript0.9/" + name + ".cy");
+            if (path != null)
+                return path;
+        }
+    }
+
+    throw new Error("Cannot find module '" + name + "'");
+};
+
+var bindings = {};
+
+process.binding = function(name) {
+    let binding = bindings[name];
+    if (typeof binding != 'undefined')
+        return binding;
+
+    switch (name) {
+        case 'buffer': binding = {
+            setupBufferJS() {
+            },
+        }; break;
+
+        case 'cares_wrap': binding = {
+        }; break;
+
+        case 'constants': binding = {
+        }; break;
+
+        case 'fs': binding = {
+            FSInitialize() {
+            },
+
+            lstat() {
+                throw new Error("stat(" + arguments[0] + ")");
+            },
+        }; break;
+
+        case 'pipe_wrap': binding = {
+        }; break;
+
+        case 'smalloc': binding = {
+            alloc() {
+            },
+        }; break;
+
+        case 'stream_wrap': binding = {
+        }; break;
+
+        case 'tcp_wrap': binding = {
+        }; break;
+
+        case 'timer_wrap': binding = {
+            kOnTimeout: 0,
+            Timer: {
+            },
+        }; break;
+
+        case 'tty_wrap': binding = {
+        }; break;
+
+        case 'uv': binding = {
+        }; break;
+
+        default:
+            throw new Error('No such module: ' + name);
+    }
+
+    bindings[name] = binding;
+    return binding;
+};
+
+let environ = *(typedef char ***)(dlsym(RTLD_DEFAULT, "environ"));
+for (let i = 0; environ[i] != null; ++i) {
+    let assign = environ[i];
+    let equal = assign.indexOf('=');
+    let name = assign.substr(0, equal);
+    let value = assign.substr(equal + 1);
+    process.env[name.toString()] = value;
+}
+
+process.pid = getpid();
+
+})();