UNPKG

kusamoji

Version:

Japanese morphological analyzer for Node.js — Viterbi tokenizer with mmap dict loading and pluggable POS-source strategy

171 lines (155 loc) 5.49 kB
"use strict"; /** * Native mmap addon loader. * * Resolution order: * 1. ~/.kusamoji/mmap_addon-{platform}-{arch}-napi{ver}.node (user cache) * 2. prebuilds/{platform}-{arch}/mmap_addon.node (shipped pre-built) * 3. build/Release/mmap_addon.node (locally compiled via node-gyp) * 4. null (fallback — NodeDictionaryLoader uses fs.readFile) * * The cache at ~/.kusamoji/ survives npm upgrades and re-installs. * A config.json alongside the binary tracks Node/N-API versions so * stale binaries are detected and skipped. */ let path = require("path"); let fs = require("fs"); let os = require("os"); const PLATFORM = process.platform; const ARCH = process.arch; const NAPI_VER = process.versions.napi; const BINARY_NAME = "mmap_addon.node"; const CACHE_BINARY = `mmap_addon-${PLATFORM}-${ARCH}-napi${NAPI_VER}.node`; const CACHE_DIR = path.join(os.homedir(), ".kusamoji"); const NATIVE_DIR = path.resolve(__dirname); /** * Try to load a .node binary. Returns the exports or null. */ function tryLoad(filePath) { try { if (!fs.existsSync(filePath)) return null; let addon = require(filePath); if (typeof addon.mmapFile === "function") return addon; } catch (e) { /* binary exists but can't load — wrong ABI, corrupt, etc. */ } return null; } /** * Read the cache config, if it exists. */ function readCacheConfig() { try { let raw = fs.readFileSync(path.join(CACHE_DIR, "config.json"), "utf8"); return JSON.parse(raw); } catch { return null; } } /** * Write cache config alongside the cached binary. */ function writeCacheConfig(source) { try { fs.mkdirSync(CACHE_DIR, { recursive: true }); fs.writeFileSync(path.join(CACHE_DIR, "config.json"), JSON.stringify({ builtAt: new Date().toISOString(), nodeVersion: process.version, napiVersion: NAPI_VER, platform: PLATFORM, arch: ARCH, source: source, }, null, 2) + "\n"); } catch { /* non-critical */ } } /** * Load the mmap addon binary. Returns { mmapFile } or null. * * @returns {object|null} */ function loadMmapAddon() { // 1. User cache (~/.kusamoji/) let cachePath = path.join(CACHE_DIR, CACHE_BINARY); let config = readCacheConfig(); if (config && config.napiVersion === NAPI_VER && config.platform === PLATFORM && config.arch === ARCH) { let addon = tryLoad(cachePath); if (addon) return addon; } // 2. Shipped prebuilt let prebuiltPath = path.join(NATIVE_DIR, "prebuilds", `${PLATFORM}-${ARCH}`, BINARY_NAME); let addon = tryLoad(prebuiltPath); if (addon) { // Cache it for future use (survives npm reinstall) try { fs.mkdirSync(CACHE_DIR, { recursive: true }); fs.copyFileSync(prebuiltPath, cachePath); writeCacheConfig("prebuilt"); } catch { /* non-critical */ } return addon; } // 3. Locally compiled (node-gyp rebuild) let localPath = path.join(NATIVE_DIR, "build", "Release", BINARY_NAME); addon = tryLoad(localPath); if (addon) { // Cache it try { fs.mkdirSync(CACHE_DIR, { recursive: true }); fs.copyFileSync(localPath, cachePath); writeCacheConfig("compiled"); } catch { /* non-critical */ } return addon; } // 4. No binary available — mmap not used return null; } /** * Install hook — called by postinstall script. * Tries prebuilt → compile → silent skip. */ function install() { let addon = loadMmapAddon(); if (addon) { console.log(`[kusamoji] mmap addon ready (${PLATFORM}-${ARCH}, N-API ${NAPI_VER})`); return true; } // Try to compile from source try { let { execSync } = require("child_process"); console.log("[kusamoji] no prebuilt binary found, compiling from source..."); execSync("npx node-gyp rebuild", { cwd: NATIVE_DIR, stdio: "inherit", timeout: 120000, }); addon = tryLoad(path.join(NATIVE_DIR, "build", "Release", BINARY_NAME)); if (addon) { try { fs.mkdirSync(CACHE_DIR, { recursive: true }); fs.copyFileSync( path.join(NATIVE_DIR, "build", "Release", BINARY_NAME), path.join(CACHE_DIR, CACHE_BINARY) ); writeCacheConfig("compiled"); } catch { } console.log(`[kusamoji] mmap addon compiled and cached at ~/.kusamoji/`); return true; } } catch (e) { // Compile failed — not fatal } console.log("[kusamoji] mmap addon not available (optional — dict loading uses fs.readFile fallback)"); return false; } // CLI mode: node loader.js --install if (require.main === module) { let arg = process.argv[2]; if (arg === "--install") { install(); } else if (arg === "--info") { let config = readCacheConfig(); console.log("Platform:", PLATFORM + "-" + ARCH); console.log("N-API:", NAPI_VER); console.log("Cache dir:", CACHE_DIR); console.log("Cache config:", config || "(none)"); console.log("Addon:", loadMmapAddon() ? "LOADED" : "NOT AVAILABLE"); } else { console.log("Usage: node loader.js --install | --info"); } } module.exports = { loadMmapAddon, install, CACHE_DIR, NATIVE_DIR };