[
  {
    "path": ".npmignore",
    "content": ".git\npackage-lock.json\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Volodymyr Shymanskyy\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![NPM version](https://img.shields.io/npm/v/inline-cpp.svg)](https://www.npmjs.com/package/inline-cpp)\n[![NPM download](https://img.shields.io/npm/dm/inline-cpp.svg)](https://www.npmjs.com/package/inline-cpp)\n[![GitHub issues](https://img.shields.io/github/issues/vshymanskyy/node-inline-cpp.svg)](https://github.com/vshymanskyy/node-inline-cpp/issues)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vshymanskyy/node-inline-cpp)\n\n# inline-cpp\nInline C++ with Node.js\n\n**Works on:** \n<img src=\"https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/linux.svg\" width=\"18\" height=\"18\" /> Linux,\n<img src=\"https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/windows.svg\" width=\"18\" height=\"18\" /> Windows,\n<img src=\"https://cdn.rawgit.com/simple-icons/simple-icons/develop/icons/apple.svg\" width=\"18\" height=\"18\" /> MacOS\n\n**Purpose:**\n- Simplify native module prototyping. Enable native code in Node.js REPL.\n- Allow JS scripts to generate C++ code and run it dynamically.\n- Popularise NAPI usage and `node-addon-api`.\n- This is **NOT** intended to be used as native module replacement!  \nIf you want to publish a native module, please package it as required by `node-gyp`.\n\n## Installation\n\n```sh\nnpm install --save inline-cpp\n```\nor install it globally (it works with Node.js REPL):\n```sh\nnpm install -g inline-cpp\n```\n\n## Usage\n\n```js\n// test.js\nconst compile = require('inline-cpp');\n\nconst hello = compile `\n  String func(const CallbackInfo& info) {\n    return String::New(info.Env(), \"Hello world from C++!\");\n  }\n`\n\nconsole.log(hello())\n```\nNow run it:\n```sh\n➜ node test.js\nHello world from C++!\n```\n\nThe first time you run the script, it takes longer to execute. For each inline block of code, a native module will be generated, compiled with `node-gyp` and loaded dynamically. If the module `Init` function is not defined, it is generated as well.  \nThe next time you run the script, it will reuse previously generated module, so it will run instantly (unless you change the inline C++ code).  \n\nFor more C++ code examples, see [node-addon-api](https://github.com/nodejs/node-addon-api#examples)  \nFor more `inline-cpp` API examples, see [examples on github](https://github.com/vshymanskyy/node-inline-cpp/tree/master/examples)\n\n## API\n\n`inline-cpp` supports several invocation methods.\n\nPass some code as string to build it with default options.\n```js\nconst InlineCPP = require('inline-cpp');\nInlineCPP('code')\n```\n\nYou can also pass code using [tagged template syntax](https://developers.google.com/web/updates/2015/01/ES6-Template-Strings#tagged_templates).\n```js\nInlineCPP `code`\n```\n\nPass an object to create a new compiler with custom options.  \nOptions will get passed to `node-gyp` target.  \n```js\nconst customCompiler = InlineCPP({ ... })\n```\n\nIf the code block only contains a single function, the compiler returns the function.  \nIf it contains multiple functions or custom `Init`, the module itself is returned.\n\n## Disclaimer\n\nThis is just a prototype. I created this to check the general concept.  \nYou're welcome to contribute! Here are some ideas:\n\n- [x] Parse/Find all functions in the block of code, add them to exports\n- [ ] Use node-gyp directly, instead of invoking `node node-gyp.js`\n- [ ] Improve error handling/reporting\n- [ ] Create advanced usage examples\n- [ ] Cleanup unused modules from cache periodically\n- [ ] ...\n\n## Debugging\n\nYou can enable debug output by setting env. variable: `DEBUG=inline-cpp`\n"
  },
  {
    "path": "examples/01_simple.js",
    "content": "const InlineCPP = require('../');\n\n// Tagged template with default options\nconst hello = InlineCPP `\n  String func(const CallbackInfo& info) {\n    return String::New(info.Env(), \"Hello world!\");\n  }\n`\n\nconsole.log(hello()); // Hello world!\n"
  },
  {
    "path": "examples/02_customCompilerOptions.js",
    "content": "const InlineCPP = require('../');\n\n// Create a compiler with some custom options to node-gyp\nconst compile = InlineCPP({ \"defines\": [`SOME_MESSAGE=\"Bazinga!\"`] });\n\nconst func = compile(`\n  String func(const CallbackInfo& info) {\n    return String::New(info.Env(), SOME_MESSAGE);\n  }\n`)\n\nconsole.log(func()); // Bazinga!\n"
  },
  {
    "path": "examples/03_customModuleInit.js",
    "content": "const InlineCPP = require('../');\n\n// Note: This is just an example.\n// You can actually remove the Init function here,\n// as the auto-generated one is the same.\n\nconst mod = InlineCPP(`\n  String alice(const CallbackInfo& info) {\n    return String::New(info.Env(), \"Alice\");\n  }\n\n  String bob(const CallbackInfo& info) {\n    return String::New(info.Env(), \"Bob\");\n  }\n\n  Object Init(Env env, Object exports) {\n    exports.Set(\"alice\", Function::New(env, alice));\n    exports.Set(\"bob\", Function::New(env, bob));\n\n    return exports;\n  }\n`);\n\nconsole.log(mod.alice(), 'and', mod.bob()); // Alice and Bob\n"
  },
  {
    "path": "index.js",
    "content": "const os = require('os');\nconst fs = require('fs-extra');\nconst path = require('path');\nconst crypto = require('crypto');\nconst { execSync } = require('child_process');\nconst paths = require('env-paths')('nodejs-inline-cpp', {suffix: ''});\nconst findParentDir = require('find-parent-dir');\nconst debug = require('debug')('inline-cpp');\nconst _ = require('lodash');\n\nlet nodeAddon, nodeGyp;\n\nfunction findBuildDeps() {\n  if (nodeAddon && nodeGyp) return;\n\n  nodeAddon = require.resolve('node-addon-api');\n  nodeAddon = findParentDir.sync(nodeAddon, 'package.json');\n\n  nodeGyp = require.resolve('node-gyp');\n  nodeGyp = findParentDir.sync(nodeGyp, 'package.json');\n  nodeGyp = path.join(nodeGyp, 'bin', 'node-gyp.js');\n\n  debug('Using node-gyp:', nodeGyp);\n  debug('Using node-addon-api:', nodeAddon);\n  \n  // For some reason, windows needs path to be escaped\n  if (os.platform() === 'win32') {\n    nodeAddon = nodeAddon.replace(/[\\\\$'\"]/g, \"\\\\$&\")\n  }\n}\n\nfunction optsMerge(objValue, srcValue) {\n  if (_.isArray(objValue)) {\n    return objValue.concat(srcValue);\n  }\n}\n\nfunction generateModule(code, opts) {\n\n  opts = opts || {};\n\n  code = code.trim();\n\n  // The stripped code is only used for some auto-detection, it is not actually compiled!\n  const strippedCode = ' ' + code\n    .replace(/,/g, ' , ')\n    .replace(/\\(/g, ' ( ')\n    .replace(/\\)/g, ' ) ')\n    .replace(/{/g, ' { ')\n    .replace(/}/g, ' } ')\n    .replace(/\\s\\s+/g, ' ') + ' ';\n  \n  // Find all function declarations\n  let funcsRe = /(([a-zA-Z_][\\w:]*)\\s+([a-zA-Z_]\\w*)\\s*\\(\\s*((?:[a-zA-Z0-9_:&\\*,\\s])*)\\s*\\))\\s*{/gm;\n  let m;\n  let funcs = [];\n  let funcSingle, funcInit;\n  \n  while ((m = funcsRe.exec(strippedCode)) !== null) {\n    // This is necessary to avoid infinite loops with zero-width matches\n    if (m.index === funcsRe.lastIndex) {\n      funcsRe.lastIndex++;\n    }\n    const func = {\n      signature: m[1],\n      name: m[3],\n      returns: m[2],\n      arguments: m[4]\n    }\n    debug('Function:', func.signature);\n\n    if (func.name === 'Init') {\n      funcInit = func;\n    } else {\n      funcs.push(func);\n    }\n  }\n\n  if (funcs.length === 1) funcSingle = funcs[0];\n\n  let init = '';\n\n  // If init function is not provided, generate it\n  if (!funcInit) {\n    init = 'Object Init(Env env, Object exports) {\\n';\n\n    for (let f of funcs) {\n      init += `  exports.Set(\"${f.name}\", Function::New(env, ${f.name}));\\n`;\n    }\n\n    init += '  return exports;\\n'\n    init += '}\\n';\n  }\n\n  let body =\n`\n#include <napi.h>\nusing namespace Napi;\n\n${code}\n${init}\n\nNODE_API_MODULE(addon, Init)\n`;\n\n  // Generate a hash using actual code and build options\n  const modName = 'm_' + crypto.createHash('sha1').update(JSON.stringify(opts)).update(body).digest(\"hex\");\n\n  const modPath = path.join(paths.cache, modName);\n  const modNode = path.join(modPath, modName+'.node');\n\n  // If the same hash exists, try loading it\n  if (fs.existsSync(modNode)) {\n    debug('Loading cached', modPath);\n    try {\n      if (funcSingle && !funcInit) {\n        return require(modNode)[funcSingle.name];\n      } else {\n        return require(modNode);\n      }\n    } catch(e) {}\n  }\n\n  // Ok no luck, let's build it...\n\n  findBuildDeps();\n\n  let gypTarget = {\n    \"target_name\": modName,\n    \"sources\": [\n      \"module.cpp\"\n    ],\n    \"include_dirs\": [\n      `<!@(node -p \"require('${nodeAddon}').include\")`\n    ],\n    \"dependencies\": [\n      `<!(node -p \"require('${nodeAddon}').gyp\")`\n    ],\n    \"cflags!\": [\"-fno-exceptions\"],\n    \"cflags_cc!\": [\"-fno-exceptions\"],\n    \"xcode_settings\": {\n      \"GCC_ENABLE_CPP_EXCEPTIONS\": \"YES\",\n      \"CLANG_CXX_LIBRARY\": \"libc++\",\n      \"MACOSX_DEPLOYMENT_TARGET\": \"10.7\",\n    },\n    \"msvs_settings\": {\n      \"VCCLCompilerTool\": { \"ExceptionHandling\": 1 },\n    },\n    \"defines\": [\"NAPI_CPP_EXCEPTIONS\"],\n  }\n  \n  gypTarget = _.mergeWith(gypTarget, opts, optsMerge)\n\n  let binding = {\n    \"targets\": [ gypTarget ]\n  };\n\n  debug('Building', modPath);\n\n  fs.ensureDirSync(modPath);\n\n  fs.writeFileSync(path.join(modPath, 'module.cpp'), body);\n  fs.writeJsonSync(path.join(modPath, 'binding.gyp'), binding, { spaces: 2 });\n\n  let execOpts = {\n    stdio: (debug.enabled) ? [0,1,2] : [null,null,null]\n  };\n\n  execSync(`node \"${nodeGyp}\" configure --directory=\"${modPath}\"`, execOpts)\n\n  try {\n    execSync(`node \"${nodeGyp}\" build --directory=\"${modPath}\"`, execOpts)\n\n    fs.renameSync(path.join(modPath, 'build', 'Release', modName+'.node'), modNode)\n    fs.removeSync(path.join(modPath, 'build'))\n\n    if (funcSingle && !funcInit) {\n      return require(modNode)[funcSingle.name];\n    } else {\n      return require(modNode);\n    }\n  } catch (e) {\n    throw new Error('C++ build failed')\n  }\n}\n\nfunction compiler(opts) {\n\n  return function(obj) {\n    let compileString;\n    // Handle tagged template invocation\n    if (Array.isArray(obj) && Array.isArray(obj.raw)) {\n      let interpVals = [].concat(Array.prototype.slice.call(arguments)).slice(1);\n      compileString = obj[0];\n      for (let i = 0, l = interpVals.length; i < l; i++) {\n        compileString += '' + interpVals[i] + obj[i + 1];\n      }\n    } else if (typeof obj === 'string' || obj instanceof String) {\n      compileString = obj;\n    }\n    \n    if (compileString) {\n      return generateModule(compileString, opts);\n    }\n    \n    throw new Error('Wrong arguments for inline-cpp')\n  }\n}\n\nmodule.exports = function(obj) {\n  if (typeof obj === 'object' &&\n      !Array.isArray(obj)\n  ) {\n    return compiler(obj)\n  }\n\n  return compiler()(obj)\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"inline-cpp\",\n  \"version\": \"0.1.8\",\n  \"description\": \"Use inline C++ in your JS\",\n  \"author\": \"Volodymyr Shymanskyy\",\n  \"license\": \"MIT\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"dependencies\": {\n    \"debug\": \"^3.1.0\",\n    \"env-paths\": \"^1.0.0\",\n    \"find-parent-dir\": \"^0.3.0\",\n    \"fs-extra\": \"^7.0.0\",\n    \"lodash\": \"^4.17.10\",\n    \"node-addon-api\": \"^1.4.0\",\n    \"node-gyp\": \"^3.7.0\"\n  },\n  \"keywords\": [\n    \"inline\",\n    \"C++\",\n    \"cpp\",\n    \"native\",\n    \"NAPI\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/vshymanskyy/node-inline-cpp\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/vshymanskyy/node-inline-cpp/issues\"\n  }\n}\n"
  }
]