<commit_before>const {app, ipcMain, session, webContents, BrowserWindow} = require('electron')
const {getAllWebContents} = process.atomBinding('web_contents')
const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllWebContents()

const fs = require('fs')
const path = require('path')
const url = require('url')

// TODO(zcbenz): Remove this when we have Object.values().
const objectValues = function (object) {
  return Object.keys(object).map(function (key) { return object[key] })
}

// Mapping between extensionId(hostname) and manifest.
const manifestMap = {}  // extensionId => manifest
const manifestNameMap = {}  // name => manifest

const generateExtensionIdFromName = function (name) {
  return name.replace(/[\W_]+/g, '-').toLowerCase()
}

const isWindowOrWebView = function (webContents) {
  const type = webContents.getType()
  return type === 'window' || type === 'webview'
}

// Create or get manifest object from |srcDirectory|.
const getManifestFromPath = function (srcDirectory) {
  let manifest
  let manifestContent

  try {
    manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'))
  } catch (readError) {
    console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`)
    console.warn(readError.stack || readError)
    throw readError
  }

  try {
    manifest = JSON.parse(manifestContent)
  } catch (parseError) {
    console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`)
    console.warn(parseError.stack || parseError)
    throw parseError
  }

  if (!manifestNameMap[manifest.name]) {
    const extensionId = generateExtensionIdFromName(manifest.name)
    manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
    Object.assign(manifest, {
      srcDirectory: srcDirectory,
      extensionId: extensionId,
      // We can not use 'file://' directly because all resources in the extension
      // will be treated as relative to the root in Chrome.
      startPage: url.format({
        protocol: 'chrome-extension',
        slashes: true,
        hostname: extensionId,
        pathname: manifest.devtools_page
      })
    })
    return manifest
  } else if (manifest && manifest.name) {
    console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`)
  }
}

// Manage the background pages.
const backgroundPages = {}

const startBackgroundPages = function (manifest) {
  if (backgroundPages[manifest.extensionId] || !manifest.background) return

  let html
  if (manifest.background.page) {
    html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page))
  } else {
    const scripts = manifest.background.scripts.map((name) => {
      return `<script src="${name}"></script>`
    }).join('')
    html = new Buffer(`<html><body>${scripts}</body></html>`)
  }

  const contents = webContents.create({
    isBackgroundPage: true,
    commandLineSwitches: ['--background-page']
  })
  backgroundPages[manifest.extensionId] = { html: html, webContents: contents }
  contents.loadURL(url.format({
    protocol: 'chrome-extension',
    slashes: true,
    hostname: manifest.extensionId,
    pathname: '_generated_background_page.html'
  }))
}

const removeBackgroundPages = function (manifest) {
  if (!backgroundPages[manifest.extensionId]) return

  backgroundPages[manifest.extensionId].webContents.destroy()
  delete backgroundPages[manifest.extensionId]
}

const sendToBackgroundPages = function (...args) {
  for (const page of objectValues(backgroundPages)) {
    page.webContents.sendToAll(...args)
  }
}

// Dispatch web contents events to Chrome APIs
const hookWebContentsEvents = function (webContents) {
  const tabId = webContents.id

  sendToBackgroundPages('CHROME_TABS_ONCREATED')

  webContents.on('will-navigate', (event, url) => {
    sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
      frameId: 0,
      parentFrameId: -1,
      processId: webContents.getId(),
      tabId: tabId,
      timeStamp: Date.now(),
      url: url
    })
  })

  webContents.on('did-navigate', (event, url) => {
    sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
      frameId: 0,
      parentFrameId: -1,
      processId: webContents.getId(),
      tabId: tabId,
      timeStamp: Date.now(),
      url: url
    })
  })

  webContents.once('destroyed', () => {
    sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId)
  })
}

// Handle the chrome.* API messages.
let nextId = 0

ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
  const page = backgroundPages[extensionId]
  if (!page) {
    console.error(`Connect to unknown extension ${extensionId}`)
    return
  }

  const portId = ++nextId
  event.returnValue = {tabId: page.webContents.id, portId: portId}

  event.sender.once('render-view-deleted', () => {
    if (page.webContents.isDestroyed()) return
    page.webContents.sendToAll(`CHROME_PORT_DISCONNECT_${portId}`)
  })
  page.webContents.sendToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo)
})

ipcMain.on('CHROME_I18N_MANIFEST', function (event, extensionId) {
  event.returnValue = manifestMap[extensionId]
})

ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message) {
  const page = backgroundPages[extensionId]
  if (!page) {
    console.error(`Connect to unknown extension ${extensionId}`)
    return
  }

  page.webContents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message)
})

ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBackgroundPage, message) {
  const contents = webContents.fromId(tabId)
  if (!contents) {
    console.error(`Sending message to unknown tab ${tabId}`)
    return
  }

  const senderTabId = isBackgroundPage ? null : event.sender.id

  contents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message)
})

ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, tabId, extensionId, details) {
  const contents = webContents.fromId(tabId)
  if (!contents) {
    console.error(`Sending message to unknown tab ${tabId}`)
    return
  }

  let code, url
  if (details.file) {
    const manifest = manifestMap[extensionId]
    code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)))
    url = `chrome-extension://${extensionId}${details.file}`
  } else {
    code = details.code
    url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`
  }

  contents.send('CHROME_TABS_EXECUTESCRIPT', event.sender.id, requestId, extensionId, url, code)
})

// Transfer the content scripts to renderer.
const contentScripts = {}

const injectContentScripts = function (manifest) {
  if (contentScripts[manifest.name] || !manifest.content_scripts) return

  const readArrayOfFiles = function (relativePath) {
    return {
      url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
      code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
    }
  }

  const contentScriptToEntry = function (script) {
    return {
      matches: script.matches,
      js: script.js.map(readArrayOfFiles),
      runAt: script.run_at || 'document_idle'
    }
  }

  try {
    const entry = {
      extensionId: manifest.extensionId,
      contentScripts: manifest.content_scripts.map(contentScriptToEntry)
    }
    contentScripts[manifest.name] = renderProcessPreferences.addEntry(entry)
  } catch (e) {
    console.error('Failed to read content scripts', e)
  }
}

const removeContentScripts = function (manifest) {
  if (!contentScripts[manifest.name]) return

  renderProcessPreferences.removeEntry(contentScripts[manifest.name])
  delete contentScripts[manifest.name]
}

// Transfer the |manifest| to a format that can be recognized by the
// |DevToolsAPI.addExtensions|.
const manifestToExtensionInfo = function (manifest) {
  return {
    startPage: manifest.startPage,
    srcDirectory: manifest.srcDirectory,
    name: manifest.name,
    exposeExperimentalAPIs: true
  }
}

// Load the extensions for the window.
const loadExtension = function (manifest) {
  startBackgroundPages(manifest)
  injectContentScripts(manifest)
}

const loadDevToolsExtensions = function (win, manifests) {
  if (!win.devToolsWebContents) return

  manifests.forEach(loadExtension)

  const extensionInfoArray = manifests.map(manifestToExtensionInfo)
  win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
}

app.on('web-contents-created', function (event, webContents) {
  if (!isWindowOrWebView(webContents)) return

  hookWebContentsEvents(webContents)
  webContents.on('devtools-opened', function () {
    loadDevToolsExtensions(webContents, objectValues(manifestMap))
  })
})

// The persistent path of "DevTools Extensions" preference file.
let loadedExtensionsPath = null

app.on('will-quit', function () {
  try {
    const loadedExtensions = objectValues(manifestMap).map(function (manifest) {
      return manifest.srcDirectory
    })
    if (loadedExtensions.length > 0) {
      try {
        fs.mkdirSync(path.dirname(loadedExtensionsPath))
      } catch (error) {
        // Ignore error
      }
      fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions))
    } else {
      fs.unlinkSync(loadedExtensionsPath)
    }
  } catch (error) {
    // Ignore error
  }
})

// We can not use protocol or BrowserWindow until app is ready.
app.once('ready', function () {
  // The chrome-extension: can map a extension URL request to real file path.
  const chromeExtensionHandler = function (request, callback) {
    const parsed = url.parse(request.url)
    if (!parsed.hostname || !parsed.path) return callback()

    const manifest = manifestMap[parsed.hostname]
    if (!manifest) return callback()

    if (parsed.path === '/_generated_background_page.html' &&
        backgroundPages[parsed.hostname]) {
      return callback({
        mimeType: 'text/html',
        data: backgroundPages[parsed.hostname].html
      })
    }

    fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
      if (err) {
        return callback(-6)  // FILE_NOT_FOUND
      } else {
        return callback(content)
      }
    })
  }
  session.on('session-created', function (ses) {
    ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
      if (error) {
        console.error(`Unable to register chrome-extension protocol: ${error}`)
      }
    })
  })

  // Load persisted extensions.
  loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
  try {
    const loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath))
    if (Array.isArray(loadedExtensions)) {
      for (const srcDirectory of loadedExtensions) {
        // Start background pages and set content scripts.
        const manifest = getManifestFromPath(srcDirectory)
        loadExtension(manifest)
      }
    }
  } catch (error) {
    // Ignore error
  }

  // The public API to add/remove extensions.
  BrowserWindow.addDevToolsExtension = function (srcDirectory) {
    const manifest = getManifestFromPath(srcDirectory)
    if (manifest) {
      loadExtension(manifest)
      for (const webContents of getAllWebContents()) {
        if (isWindowOrWebView(webContents)) {
          loadDevToolsExtensions(webContents, [manifest])
        }
      }
      return manifest.name
    }
  }

  BrowserWindow.removeDevToolsExtension = function (name) {
    const manifest = manifestNameMap[name]
    if (!manifest) return

    removeBackgroundPages(manifest)
    removeContentScripts(manifest)
    delete manifestMap[manifest.extensionId]
    delete manifestNameMap[name]
  }

  BrowserWindow.getDevToolsExtensions = function () {
    const extensions = {}
    Object.keys(manifestNameMap).forEach(function (name) {
      const manifest = manifestNameMap[name]
      extensions[name] = {name: manifest.name, version: manifest.version}
    })
    return extensions
  }
})
<commit_msg>Use background page path instead of generated path<commit_after>const {app, ipcMain, session, webContents, BrowserWindow} = require('electron')
const {getAllWebContents} = process.atomBinding('web_contents')
const renderProcessPreferences = process.atomBinding('render_process_preferences').forAllWebContents()

const fs = require('fs')
const path = require('path')
const url = require('url')

// TODO(zcbenz): Remove this when we have Object.values().
const objectValues = function (object) {
  return Object.keys(object).map(function (key) { return object[key] })
}

// Mapping between extensionId(hostname) and manifest.
const manifestMap = {}  // extensionId => manifest
const manifestNameMap = {}  // name => manifest

const generateExtensionIdFromName = function (name) {
  return name.replace(/[\W_]+/g, '-').toLowerCase()
}

const isWindowOrWebView = function (webContents) {
  const type = webContents.getType()
  return type === 'window' || type === 'webview'
}

// Create or get manifest object from |srcDirectory|.
const getManifestFromPath = function (srcDirectory) {
  let manifest
  let manifestContent

  try {
    manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'))
  } catch (readError) {
    console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`)
    console.warn(readError.stack || readError)
    throw readError
  }

  try {
    manifest = JSON.parse(manifestContent)
  } catch (parseError) {
    console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`)
    console.warn(parseError.stack || parseError)
    throw parseError
  }

  if (!manifestNameMap[manifest.name]) {
    const extensionId = generateExtensionIdFromName(manifest.name)
    manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
    Object.assign(manifest, {
      srcDirectory: srcDirectory,
      extensionId: extensionId,
      // We can not use 'file://' directly because all resources in the extension
      // will be treated as relative to the root in Chrome.
      startPage: url.format({
        protocol: 'chrome-extension',
        slashes: true,
        hostname: extensionId,
        pathname: manifest.devtools_page
      })
    })
    return manifest
  } else if (manifest && manifest.name) {
    console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`)
  }
}

// Manage the background pages.
const backgroundPages = {}

const startBackgroundPages = function (manifest) {
  if (backgroundPages[manifest.extensionId] || !manifest.background) return

  let html
  let name
  if (manifest.background.page) {
    name = manifest.background.page
    html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page))
  } else {
    name = '_generated_background_page.html'
    const scripts = manifest.background.scripts.map((name) => {
      return `<script src="${name}"></script>`
    }).join('')
    html = new Buffer(`<html><body>${scripts}</body></html>`)
  }

  const contents = webContents.create({
    isBackgroundPage: true,
    commandLineSwitches: ['--background-page']
  })
  backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name }
  contents.loadURL(url.format({
    protocol: 'chrome-extension',
    slashes: true,
    hostname: manifest.extensionId,
    pathname: name
  }))
}

const removeBackgroundPages = function (manifest) {
  if (!backgroundPages[manifest.extensionId]) return

  backgroundPages[manifest.extensionId].webContents.destroy()
  delete backgroundPages[manifest.extensionId]
}

const sendToBackgroundPages = function (...args) {
  for (const page of objectValues(backgroundPages)) {
    page.webContents.sendToAll(...args)
  }
}

// Dispatch web contents events to Chrome APIs
const hookWebContentsEvents = function (webContents) {
  const tabId = webContents.id

  sendToBackgroundPages('CHROME_TABS_ONCREATED')

  webContents.on('will-navigate', (event, url) => {
    sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
      frameId: 0,
      parentFrameId: -1,
      processId: webContents.getId(),
      tabId: tabId,
      timeStamp: Date.now(),
      url: url
    })
  })

  webContents.on('did-navigate', (event, url) => {
    sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
      frameId: 0,
      parentFrameId: -1,
      processId: webContents.getId(),
      tabId: tabId,
      timeStamp: Date.now(),
      url: url
    })
  })

  webContents.once('destroyed', () => {
    sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId)
  })
}

// Handle the chrome.* API messages.
let nextId = 0

ipcMain.on('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
  const page = backgroundPages[extensionId]
  if (!page) {
    console.error(`Connect to unknown extension ${extensionId}`)
    return
  }

  const portId = ++nextId
  event.returnValue = {tabId: page.webContents.id, portId: portId}

  event.sender.once('render-view-deleted', () => {
    if (page.webContents.isDestroyed()) return
    page.webContents.sendToAll(`CHROME_PORT_DISCONNECT_${portId}`)
  })
  page.webContents.sendToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo)
})

ipcMain.on('CHROME_I18N_MANIFEST', function (event, extensionId) {
  event.returnValue = manifestMap[extensionId]
})

ipcMain.on('CHROME_RUNTIME_SENDMESSAGE', function (event, extensionId, message) {
  const page = backgroundPages[extensionId]
  if (!page) {
    console.error(`Connect to unknown extension ${extensionId}`)
    return
  }

  page.webContents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message)
})

ipcMain.on('CHROME_TABS_SEND_MESSAGE', function (event, tabId, extensionId, isBackgroundPage, message) {
  const contents = webContents.fromId(tabId)
  if (!contents) {
    console.error(`Sending message to unknown tab ${tabId}`)
    return
  }

  const senderTabId = isBackgroundPage ? null : event.sender.id

  contents.sendToAll(`CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message)
})

ipcMain.on('CHROME_TABS_EXECUTESCRIPT', function (event, requestId, tabId, extensionId, details) {
  const contents = webContents.fromId(tabId)
  if (!contents) {
    console.error(`Sending message to unknown tab ${tabId}`)
    return
  }

  let code, url
  if (details.file) {
    const manifest = manifestMap[extensionId]
    code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)))
    url = `chrome-extension://${extensionId}${details.file}`
  } else {
    code = details.code
    url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`
  }

  contents.send('CHROME_TABS_EXECUTESCRIPT', event.sender.id, requestId, extensionId, url, code)
})

// Transfer the content scripts to renderer.
const contentScripts = {}

const injectContentScripts = function (manifest) {
  if (contentScripts[manifest.name] || !manifest.content_scripts) return

  const readArrayOfFiles = function (relativePath) {
    return {
      url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
      code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
    }
  }

  const contentScriptToEntry = function (script) {
    return {
      matches: script.matches,
      js: script.js.map(readArrayOfFiles),
      runAt: script.run_at || 'document_idle'
    }
  }

  try {
    const entry = {
      extensionId: manifest.extensionId,
      contentScripts: manifest.content_scripts.map(contentScriptToEntry)
    }
    contentScripts[manifest.name] = renderProcessPreferences.addEntry(entry)
  } catch (e) {
    console.error('Failed to read content scripts', e)
  }
}

const removeContentScripts = function (manifest) {
  if (!contentScripts[manifest.name]) return

  renderProcessPreferences.removeEntry(contentScripts[manifest.name])
  delete contentScripts[manifest.name]
}

// Transfer the |manifest| to a format that can be recognized by the
// |DevToolsAPI.addExtensions|.
const manifestToExtensionInfo = function (manifest) {
  return {
    startPage: manifest.startPage,
    srcDirectory: manifest.srcDirectory,
    name: manifest.name,
    exposeExperimentalAPIs: true
  }
}

// Load the extensions for the window.
const loadExtension = function (manifest) {
  startBackgroundPages(manifest)
  injectContentScripts(manifest)
}

const loadDevToolsExtensions = function (win, manifests) {
  if (!win.devToolsWebContents) return

  manifests.forEach(loadExtension)

  const extensionInfoArray = manifests.map(manifestToExtensionInfo)
  win.devToolsWebContents.executeJavaScript(`DevToolsAPI.addExtensions(${JSON.stringify(extensionInfoArray)})`)
}

app.on('web-contents-created', function (event, webContents) {
  if (!isWindowOrWebView(webContents)) return

  hookWebContentsEvents(webContents)
  webContents.on('devtools-opened', function () {
    loadDevToolsExtensions(webContents, objectValues(manifestMap))
  })
})

// The persistent path of "DevTools Extensions" preference file.
let loadedExtensionsPath = null

app.on('will-quit', function () {
  try {
    const loadedExtensions = objectValues(manifestMap).map(function (manifest) {
      return manifest.srcDirectory
    })
    if (loadedExtensions.length > 0) {
      try {
        fs.mkdirSync(path.dirname(loadedExtensionsPath))
      } catch (error) {
        // Ignore error
      }
      fs.writeFileSync(loadedExtensionsPath, JSON.stringify(loadedExtensions))
    } else {
      fs.unlinkSync(loadedExtensionsPath)
    }
  } catch (error) {
    // Ignore error
  }
})

// We can not use protocol or BrowserWindow until app is ready.
app.once('ready', function () {
  // The chrome-extension: can map a extension URL request to real file path.
  const chromeExtensionHandler = function (request, callback) {
    const parsed = url.parse(request.url)
    if (!parsed.hostname || !parsed.path) return callback()

    const manifest = manifestMap[parsed.hostname]
    if (!manifest) return callback()

    const page = backgroundPages[parsed.hostname]
    if (page && parsed.path === `/${page.name}`) {
      return callback({
        mimeType: 'text/html',
        data: page.html
      })
    }

    fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
      if (err) {
        return callback(-6)  // FILE_NOT_FOUND
      } else {
        return callback(content)
      }
    })
  }
  session.on('session-created', function (ses) {
    ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler, function (error) {
      if (error) {
        console.error(`Unable to register chrome-extension protocol: ${error}`)
      }
    })
  })

  // Load persisted extensions.
  loadedExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
  try {
    const loadedExtensions = JSON.parse(fs.readFileSync(loadedExtensionsPath))
    if (Array.isArray(loadedExtensions)) {
      for (const srcDirectory of loadedExtensions) {
        // Start background pages and set content scripts.
        const manifest = getManifestFromPath(srcDirectory)
        loadExtension(manifest)
      }
    }
  } catch (error) {
    // Ignore error
  }

  // The public API to add/remove extensions.
  BrowserWindow.addDevToolsExtension = function (srcDirectory) {
    const manifest = getManifestFromPath(srcDirectory)
    if (manifest) {
      loadExtension(manifest)
      for (const webContents of getAllWebContents()) {
        if (isWindowOrWebView(webContents)) {
          loadDevToolsExtensions(webContents, [manifest])
        }
      }
      return manifest.name
    }
  }

  BrowserWindow.removeDevToolsExtension = function (name) {
    const manifest = manifestNameMap[name]
    if (!manifest) return

    removeBackgroundPages(manifest)
    removeContentScripts(manifest)
    delete manifestMap[manifest.extensionId]
    delete manifestNameMap[name]
  }

  BrowserWindow.getDevToolsExtensions = function () {
    const extensions = {}
    Object.keys(manifestNameMap).forEach(function (name) {
      const manifest = manifestNameMap[name]
      extensions[name] = {name: manifest.name, version: manifest.version}
    })
    return extensions
  }
})
<|endoftext|>


<commit_before>
/*
  Compilation for the browser
*/
riot.compile = (function () {

  var
    doc = window.document,
    promise,
    ready

  function GET(url, fn, opts) {
    var req = new XMLHttpRequest()

    req.onreadystatechange = function() {
      if (req.readyState === 4 &&
         (req.status === 200 || !req.status && req.responseText.length))
        fn(req.responseText, opts, url)
    }
    req.open('GET', url, true)
    req.send('')
  }

  function globalEval(js) {
    var
      node = doc.createElement('script'),
      root = doc.documentElement

    node.text = js
    root.appendChild(node)
    root.removeChild(node)
  }

  function compileScripts(fn, exopt) {
    var
      scripts = doc.querySelectorAll('script[type="riot/tag"]'),
      scriptsAmount = scripts.length

    function done() {
      promise.trigger('ready')
      ready = true
      if (fn) fn()
    }

    function compileTag(src, opts, url) {
      var code = compile(src, opts)

      if (url) code += '\n//# sourceURL=' + url + '.js'
      globalEval(code)
      if (!--scriptsAmount) done()
    }

    if (!scriptsAmount) done()
    else {
      for (var i = 0; i < scripts.length; ++i) {
        var
          script = scripts[i],
          opts = {template: script.getAttribute('template')},
          url = script.getAttribute('src')

        if (exopt) opts = extend(opts, exopt)
        url ? GET(url, compileTag, opts) : compileTag(script.innerHTML, opts)
      }
    }
  }

  //// Entry point -----

  return function (arg, fn, opts) {

    if (typeof arg === 'string') {

      if (typeof fn === 'object') {
        opts = fn
        fn = false
      }

      if (/^\s*</.test(arg)) {

        // `riot.compile(tag [, true][, options])`
        var js = compile(arg, opts)
        if (!fn) globalEval(js)
        return js
      }

      // `riot.compile(url [, callback][, options])`
      GET(arg, function (str) {
        var js = compile(str, opts, arg)
        globalEval(js)
        if (fn) fn(js, str)
      })

    }
    else {

      // `riot.compile([callback][, options])`

      if (typeof arg === 'function') {
        opts = fn
        fn = arg
      }
      else {
        opts = arg
        fn = undefined
      }

      if (ready)
        return fn && fn()

      if (promise) {
        if (fn) promise.on('ready', fn)

      } else {
        promise = riot.observable()
        compileScripts(fn, opts)
      }
    }
  }

})()

// reassign mount methods -----
var mount = riot.mount

riot.mount = function(a, b, c) {
  var ret
  riot.compile(function() { ret = mount(a, b, c) })
  return ret
}
<commit_msg>Implements #905 and #1070<commit_after>
/*
  Compilation for the browser
*/
riot.compile = (function () {

  var
    doc = window.document,
    promise,
    ready

  function GET(url, fn, opts) {
    var req = new XMLHttpRequest()

    req.onreadystatechange = function() {
      if (req.readyState === 4 &&
         (req.status === 200 || !req.status && req.responseText.length))
        fn(req.responseText, opts, url)
    }
    req.open('GET', url, true)
    req.send('')
  }

  function globalEval(js) {
    var
      node = doc.createElement('script'),
      root = doc.documentElement

    node.text = js
    root.appendChild(node)
    root.removeChild(node)
  }

  function compileScripts(fn, exopt) {
    var
      scripts = doc.querySelectorAll('script[type="riot/tag"]'),
      scriptsAmount = scripts.length

    function done() {
      promise.trigger('ready')
      ready = true
      if (fn) fn()
    }

    function compileTag(src, opts, url) {
      var code = compile(src, opts, url)

      if (url) code += '\n//# sourceURL=' + url + '.js'
      globalEval(code)
      if (!--scriptsAmount) done()
    }

    if (!scriptsAmount) done()
    else {
      for (var i = 0; i < scripts.length; ++i) {
        var
          script = scripts[i],
          opts = {template: script.getAttribute('template')},
          url = script.getAttribute('src')

        if (exopt) opts = extend(opts, exopt)
        url ? GET(url, compileTag, opts) : compileTag(script.innerHTML, opts)
      }
    }
  }

  //// Entry point -----

  return function (arg, fn, opts) {

    if (typeof arg === 'string') {

      if (typeof fn === 'object') {
        opts = fn
        fn = false
      }

      if (/^\s*</.test(arg)) {

        // `riot.compile(tag [, true][, options])`
        var js = compile(arg, opts)
        if (!fn) globalEval(js)
        return js
      }

      // `riot.compile(url [, callback][, options])`
      GET(arg, function (str) {
        var js = compile(str, opts, arg)
        globalEval(js)
        if (fn) fn(js, str)
      })

    }
    else {

      // `riot.compile([callback][, options])`

      if (typeof arg === 'function') {
        opts = fn
        fn = arg
      }
      else {
        opts = arg
        fn = undefined
      }

      if (ready)
        return fn && fn()

      if (promise) {
        if (fn) promise.on('ready', fn)

      } else {
        promise = riot.observable()
        compileScripts(fn, opts)
      }
    }
  }

})()

// reassign mount methods -----
var mount = riot.mount

riot.mount = function(a, b, c) {
  var ret
  riot.compile(function() { ret = mount(a, b, c) })
  return ret
}
<|endoftext|>


<commit_before>/*
  Analyse ast, create scopes
*/

var hasOwnProperty = Object.prototype.hasOwnProperty;

//
// Scope class
//

function Scope(type, parentScope, thisObject){
  this.type = type || 'unknown';
  this.thisObject = thisObject;
  this.subscopes = [];

  if (parentScope)
  {
    this.parent = parentScope;
    this.root = parentScope.root;
    parentScope.subscopes.push(this);
  }
  else
  {
    this.root = this;
  }

  if (parentScope)
  {
    var Names = Function();
    Names.prototype = parentScope.names;
    this.names = new Names();
  }
  else
    this.names = {};
}

Scope.prototype = {
  root: null,
  parent: null,
  subscopes: null,
  thisObject: null,
  names: null,

  getNames: function(){
    var result = {};

    for (var name in this.names)
      result[name] = this.names[name];

    return result;
  },
  scopeByName: function(name){
    if (name in this.names)
    {
      var cursor = this;
      while (cursor)
      {
        if (hasOwnProperty.call(cursor.names, name)) // hasOwnProperty may be overriden
          return cursor;

        cursor = cursor.parent;
      }
    }
  },
  has: function(name){
    return !!this.scopeByName(name);
  },
  hasOwn: function(name){
    return this.names.hasOwnProperty(name);
  },
  get: function(name){
    if (this.has(name))
      return this.names[name];
  },
  token: function(name){
    var ref = this.get(name);
    return ref && ref.token;
  },
  put: function(name, type, token){
    var cur = this.get(name);

    if (cur)
    {
      if (type == 'defun')
        return;
    }

    var ref = [type, token];
    ref.token = token || null;

    this.names[name] = ref;

    return ref;
  },
  set: function(name, token){
    var scope = this.scopeByName(name);
    if (scope)
    {
      var ref = scope.names[name];
      ref[1] = token;
      ref.token = token;
    }
  },

  resolve: function(token){
    var path = [];
    var cursor = token;

    cycle: while (cursor && !cursor.obj)
    {
      switch (cursor[0])
      {
        case 'name':
          var nameScope = cursor.scope || this.scopeByName(cursor[1]);

          if (!nameScope)
            return;

          if (nameScope === this)
          {
            cursor = this.names[cursor[1]];
            if (cursor)
              cursor = cursor.token;
          }
          else
            cursor = nameScope.resolve(cursor);

          break cycle;

        case 'dot':
          path.unshift(cursor[2]);
          cursor = cursor[1];
          break;

        case 'sub':
          //debugger;
          var val = this.resolve(cursor[2]);
          if (val && (val[0] == 'string' || val[0] == 'num'))
          {
            //console.log('sub', val[1], cursor);
            path.unshift(val[1]);
          }
          else
            return;

          cursor = cursor[1];

          break;

        case 'string':
        case 'num':
        case 'regexp':
        case 'defun':
        case 'function':
        case 'object':
        case 'array':
          break cycle;

        default:
          return;
      }
    }

    if (cursor && path.length)
    {
      if (!cursor.obj)
        return;

      for (var i = 0, key; key = path[i]; i++)
        if (cursor.obj && key in cursor.obj && cursor.obj[key])
          cursor = cursor.obj[key];
        else
          return;
    }

    return cursor;
  },

  isLocal: function(name){
    return this.names.hasOwnProperty(name);
  },
  isSpecial: function(name){
    name = this.get(name);
    return name && name.type == 'special';
  },
  isGlobal: function(name){
    return this.has(name) && this.get(name) === this.root.get(name);
  }
};

//
// Main function
//

var walker = require('./walker').ast_walker();
var runFunction = require('./structure').process;

function process(ast, scope){
  //
  // walkers
  //

  /*function createRunner(func){
    return function(token, t, args){
      var oldScope = func.scope;
      func.scope = new Scope('', oldScope);

      func.scope.put('arguments', 'sys', ['array', args]);
      for (var i = 0, params = func[2]; param = params[i]; i++)
        func.scope.put(param, 'arg', args[i]);

      debugger;
      runFunction(func);

      func.scope = oldScope;
    }
  }*/
  /*function createRun(fn, name){
    var result = ['function', name || null, []];
    result.run = fn;
    return result;
  }

  var objectProto = {
    hasOwnProperty: createRun(function(token, t, args){
      var res = Object.prototype.hasOwnProperty.call(t.obj, args[0].obj);
      token.obj = ['name', res ? 'true' : 'false'];
    }, 'hasOwnProperty'),
    self: createRun(function(token, t, args){
      token.obj = t.obj;
    }, 'self')
  };

  function createObj(proto){
    var F = Function();
    F.prototype = proto;
    return new F;
  }*/

  var fn_walker = function(token){
    var name = token[1];
    var args = token[2];

    if (name)
      this.scope.put(name, 'defun', token);

    var scope = new Scope('function', this.scope);
    for (var i = 0; i < args.length; i++)
      scope.put(args[i], 'arg');

    token.scope = scope;
    /*if (name)
      token.run = createRunner(token);*/
  }

  var var_walker = function(token){
    var defs = token[1];

    for (var i = 0, def; def = defs[i]; i++)
    {
      var name = def[0];
      var val = def[1];

      //if (val)
      //  val = this.walk(def, 1);

      this.scope.put(name, 'var', val);
    }

    //return token;
  }

  //
  // main part
  //

  ast.scope = scope;

  return walker.walk(ast, {
    'var': var_walker,
    'const': var_walker,
    'defun': fn_walker,
    'function': fn_walker,

    'name': function(token){
      token.scope = this.scope;
      //var ref = this.scope.get(token[1]);
      //return (ref && ref.token) || token;
    },

    /*'object': function(token){
      console.log('!');
      token.obj = createObj(objectProto);
      for (var i = 0, props = token[1], prop; prop = props[i]; i++)
        token.obj[prop[0]] = prop[1];
    },
    'string': function(token){
      token.obj = token[1];
    },*/

    /*
    name: function(token){
      var name = token[1];

      var ret = scope.has(name) && scope.get(name)[1];

      if (ret && resolveNameRef(ret)[0] != name)
      {
        // TODO: check for name conflict, it base name not in scope
        token.splice.apply(token, [0, token.length].concat(ret));
        return;
      }

      if (scope.isRoot(name))
      {
        var pos = walkerStack.length - 1;
        var path = [name];
        var cursor;
        while (cursor = walkerStack[--pos])
        {
          if (cursor[0] != 'dot')
            break;

          path.push(cursor[2]);
          putCodePath(path.join('.'), cursor);
        }
      }
    },

    'call': function(token){

    },*/
    /*object: function(token){
      this.walk(token, 1);

      for (var i = 0, props = token[1], prop; prop = token[i]; i++)
        prop[1] = this.scope.resolve(prop[1]) || prop[1];

      return token;
    },*/

    assign: function(token){
      var op = token[1];
      var lvalue = token[2];
      var rvalue = this.walk(token, 3);

      if (lvalue[0] == 'name')
        this.scope.set(lvalue[1], rvalue);
      else
        this.walk(token, 2);

      return token;
    }
  });
}

module.exports = {
  Scope: Scope,
  process: process
};
<commit_msg>add `this` resolving<commit_after>/*
  Analyse ast, create scopes
*/

var hasOwnProperty = Object.prototype.hasOwnProperty;

//
// Scope class
//

function Scope(type, parentScope, thisObject){
  this.type = type || 'unknown';
  this.thisObject = thisObject || ['name', 'undefined'];
  this.subscopes = [];

  if (parentScope)
  {
    this.parent = parentScope;
    this.root = parentScope.root;
    parentScope.subscopes.push(this);
  }
  else
  {
    this.root = this;
  }

  if (parentScope)
  {
    var Names = Function();
    Names.prototype = parentScope.names;
    this.names = new Names();
  }
  else
    this.names = {};
}

Scope.prototype = {
  root: null,
  parent: null,
  subscopes: null,
  thisObject: null,
  names: null,

  getNames: function(){
    var result = {};

    for (var name in this.names)
      result[name] = this.names[name];

    return result;
  },
  scopeByName: function(name){
    if (name in this.names)
    {
      var cursor = this;
      while (cursor)
      {
        if (hasOwnProperty.call(cursor.names, name)) // hasOwnProperty may be overriden
          return cursor;

        cursor = cursor.parent;
      }
    }
  },
  has: function(name){
    return !!this.scopeByName(name);
  },
  hasOwn: function(name){
    return this.names.hasOwnProperty(name);
  },
  get: function(name){
    if (this.has(name))
      return this.names[name];
  },
  token: function(name){
    var ref = this.get(name);
    return ref && ref.token;
  },
  put: function(name, type, token){
    var cur = this.get(name);

    if (cur)
    {
      if (type == 'defun')
        return;
    }

    var ref = [type, token];
    ref.token = token || null;

    this.names[name] = ref;

    return ref;
  },
  set: function(name, token){
    var scope = this.scopeByName(name);
    if (scope)
    {
      var ref = scope.names[name];
      ref[1] = token;
      ref.token = token;
    }
  },

  resolve: function(token){
    var path = [];
    var cursor = token;

    cycle: while (cursor && !cursor.obj)
    {
      switch (cursor[0])
      {
        case 'name':
          if (cursor[1] == 'this')
          {
            cursor = this.thisObject;
            break cycle;
          }

          var nameScope = cursor.scope || this.scopeByName(cursor[1]);

          if (!nameScope)
            return;

          if (nameScope === this)
          {
            cursor = this.names[cursor[1]];
            if (cursor)
              cursor = cursor.token;
          }
          else
            cursor = nameScope.resolve(cursor);

          break cycle;

        case 'dot':
          path.unshift(cursor[2]);
          cursor = cursor[1];
          break;

        case 'sub':
          //debugger;
          var val = this.resolve(cursor[2]);
          if (val && (val[0] == 'string' || val[0] == 'num'))
          {
            //console.log('sub', val[1], cursor);
            path.unshift(val[1]);
          }
          else
            return;

          cursor = cursor[1];

          break;

        case 'string':
        case 'num':
        case 'regexp':
        case 'defun':
        case 'function':
        case 'object':
        case 'array':
          break cycle;

        default:
          return;
      }
    }

    if (cursor && path.length)
    {
      if (!cursor.obj)
        return;

      for (var i = 0, key; key = path[i]; i++)
        if (cursor.obj && key in cursor.obj && cursor.obj[key])
          cursor = cursor.obj[key];
        else
          return;
    }

    return cursor;
  },

  isLocal: function(name){
    return this.names.hasOwnProperty(name);
  },
  isSpecial: function(name){
    name = this.get(name);
    return name && name.type == 'special';
  },
  isGlobal: function(name){
    return this.has(name) && this.get(name) === this.root.get(name);
  }
};

//
// Main function
//

var walker = require('./walker').ast_walker();
var runFunction = require('./structure').process;

function process(ast, scope){
  //
  // walkers
  //

  /*function createRunner(func){
    return function(token, t, args){
      var oldScope = func.scope;
      func.scope = new Scope('', oldScope);

      func.scope.put('arguments', 'sys', ['array', args]);
      for (var i = 0, params = func[2]; param = params[i]; i++)
        func.scope.put(param, 'arg', args[i]);

      debugger;
      runFunction(func);

      func.scope = oldScope;
    }
  }*/
  /*function createRun(fn, name){
    var result = ['function', name || null, []];
    result.run = fn;
    return result;
  }

  var objectProto = {
    hasOwnProperty: createRun(function(token, t, args){
      var res = Object.prototype.hasOwnProperty.call(t.obj, args[0].obj);
      token.obj = ['name', res ? 'true' : 'false'];
    }, 'hasOwnProperty'),
    self: createRun(function(token, t, args){
      token.obj = t.obj;
    }, 'self')
  };

  function createObj(proto){
    var F = Function();
    F.prototype = proto;
    return new F;
  }*/

  var fn_walker = function(token){
    var name = token[1];
    var args = token[2];

    if (name)
      this.scope.put(name, 'defun', token);

    var scope = new Scope('function', this.scope);
    for (var i = 0; i < args.length; i++)
      scope.put(args[i], 'arg');

    token.scope = scope;
    /*if (name)
      token.run = createRunner(token);*/
  }

  var var_walker = function(token){
    var defs = token[1];

    for (var i = 0, def; def = defs[i]; i++)
    {
      var name = def[0];
      var val = def[1];

      //if (val)
      //  val = this.walk(def, 1);

      this.scope.put(name, 'var', val);
    }

    //return token;
  }

  //
  // main part
  //

  ast.scope = scope;

  return walker.walk(ast, {
    'var': var_walker,
    'const': var_walker,
    'defun': fn_walker,
    'function': fn_walker,

    'name': function(token){
      token.scope = this.scope;
      //var ref = this.scope.get(token[1]);
      //return (ref && ref.token) || token;
    },

    /*'object': function(token){
      console.log('!');
      token.obj = createObj(objectProto);
      for (var i = 0, props = token[1], prop; prop = props[i]; i++)
        token.obj[prop[0]] = prop[1];
    },
    'string': function(token){
      token.obj = token[1];
    },*/

    /*
    name: function(token){
      var name = token[1];

      var ret = scope.has(name) && scope.get(name)[1];

      if (ret && resolveNameRef(ret)[0] != name)
      {
        // TODO: check for name conflict, it base name not in scope
        token.splice.apply(token, [0, token.length].concat(ret));
        return;
      }

      if (scope.isRoot(name))
      {
        var pos = walkerStack.length - 1;
        var path = [name];
        var cursor;
        while (cursor = walkerStack[--pos])
        {
          if (cursor[0] != 'dot')
            break;

          path.push(cursor[2]);
          putCodePath(path.join('.'), cursor);
        }
      }
    },

    'call': function(token){

    },*/
    /*object: function(token){
      this.walk(token, 1);

      for (var i = 0, props = token[1], prop; prop = token[i]; i++)
        prop[1] = this.scope.resolve(prop[1]) || prop[1];

      return token;
    },*/

    assign: function(token){
      var op = token[1];
      var lvalue = token[2];
      var rvalue = this.walk(token, 3);

      if (lvalue[0] == 'name')
        this.scope.set(lvalue[1], rvalue);
      else
        this.walk(token, 2);

      return token;
    }
  });
}

module.exports = {
  Scope: Scope,
  process: process
};
<|endoftext|>


<commit_before>var fs = require("fs"),
	hash = require("../hash"),
	cache = {}, //filename => {"mtime": dateObject, "etag": string, "size": fileSize}
	cacheOn = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "prod";
//Adds information to the cache
function addToCache(filename, obj) {
	if(cacheOn)
	{
		var x = cache[filename];
		if(!x)
			x = cache[filename] = {};
		x.mtime = obj.mtime || x.mtime;
		x.etag = obj.etag || x.etag;
		x.size = obj.size || x.size;
	}
}
//Sets the Last-Modified date based upon the mtime of the file
exports.lastModified = function(filename) {
	if(cache[filename] && cache[filename].lastModified)
		return cache[filename].lastModified;
	var stat = fs.statSync(filename);
	addToCache(filename, stat);
	return stat.mtime;
};
//Sets the ETag based upon the file size and CRC-32 hash
exports.etag = function(filename) {
	if(cache[filename] && cache[filename].etag)
		return cache[filename].etag;
	var data = fs.readFileSync(filename),
		size;
	if(cache[filename] && cache[filename].size)
		size = cache[filename].size;
	else
	{
		var stat = fs.statSync(filename);
		addToCache(filename, stat);
		size = stat.size;
	}
	return new Number(size).toString(36) + "-" +
		parseInt(hash(data, "crc32"), 16).toString(36);
};
//Set expiration date to one year from now
exports.expires = function(filename) {
	var d = new Date();
	d.setFullYear(d.getFullYear() + 1);
	return d;
};
//Set the fingerprint based upon the ETag of the file
exports.fileFingerprint = function(filename, fullPath) {
	return filename + "?v=" + exports.etag(fullPath);
};

/* Example of setting the fingerprint based upon the Last
	Modified date of the file

exports.fileFingerprint = function(filename, fullPath) {
	//Encode the lastModified Date as a radix 36 UTC timestamp
	var mdate = exports.lastModified(fullPath);
	mdate.setMilliseconds(0);
	mdate = new Number(mdate.getTime() / 1000).toString(36);
	return filename + "?v=" + mdate;
};
*/
<commit_msg>Make sure to cache the etag value.<commit_after>var fs = require("fs"),
	hash = require("../hash"),
	cache = {}, //filename => {"mtime": dateObject, "etag": string, "size": fileSize}
	cacheOn = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "prod";
//Adds information to the cache
function addToCache(filename, obj) {
	if(cacheOn)
	{
		var x = cache[filename];
		if(!x)
			x = cache[filename] = {};
		x.mtime = obj.mtime || x.mtime;
		x.etag = obj.etag || x.etag;
		x.size = obj.size || x.size;
	}
}
//Sets the Last-Modified date based upon the mtime of the file
exports.lastModified = function(filename) {
	if(cache[filename] && cache[filename].lastModified)
		return cache[filename].lastModified;
	var stat = fs.statSync(filename);
	addToCache(filename, stat);
	return stat.mtime;
};
//Sets the ETag based upon the file size and CRC-32 hash
exports.etag = function(filename) {
	if(cache[filename] && cache[filename].etag)
		return cache[filename].etag;
	var data = fs.readFileSync(filename),
		size;
	if(cache[filename] && cache[filename].size)
		size = cache[filename].size;
	else
	{
		var stat = fs.statSync(filename);
		addToCache(filename, stat);
		size = stat.size;
	}
	var etag = new Number(size).toString(36) + "-" +
		parseInt(hash(data, "crc32"), 16).toString(36);
	addToCache(filename, {etag: etag});
	return etag;
};
//Set expiration date to one year from now
exports.expires = function(filename) {
	var d = new Date();
	d.setFullYear(d.getFullYear() + 1);
	return d;
};
//Set the fingerprint based upon the ETag of the file
exports.fileFingerprint = function(filename, fullPath) {
	return filename + "?v=" + exports.etag(fullPath);
};

/* Example of setting the fingerprint based upon the Last
	Modified date of the file

exports.fileFingerprint = function(filename, fullPath) {
	//Encode the lastModified Date as a radix 36 UTC timestamp
	var mdate = exports.lastModified(fullPath);
	mdate.setMilliseconds(0);
	mdate = new Number(mdate.getTime() / 1000).toString(36);
	return filename + "?v=" + mdate;
};
*/
<|endoftext|>


<commit_before>var demand = require('must'),
	NumberType = require('../NumberType');

exports.initList = function(List) {
	List.add({
		number: { type: NumberType },
		nested: {
			number: { type: NumberType }
		}
	});
};

exports.testFieldType = function(List) {
	var testItem = new List.model();

	it('should validate numeric input', function() {
		demand(List.fields.number.validateInput({
			number: 0
		})).be(true);
		demand(List.fields.number.validateInput({
			number: 1
		})).be(true);
		demand(List.fields.number.validateInput({
			number: -1
		})).be(true);
		demand(List.fields.number.validateInput({
			number: 1.1
		})).be(true);
	});

	it('should validate string input', function() {
		demand(List.fields.number.validateInput({
			number: '0'
		})).be(true);
		demand(List.fields.number.validateInput({
			number: '1'
		})).be(true);
		demand(List.fields.number.validateInput({
			number: '-1'
		})).be(true);
		demand(List.fields.number.validateInput({
			number: '1.1'
		})).be(true);
	});

	it('should validate no input', function() {
		demand(List.fields.number.validateInput({})).be(true);
		demand(List.fields.number.validateInput({}, true)).be(false);
		testItem.number = 1;
		demand(List.fields.number.validateInput({}, true, testItem)).be(true);
		testItem.number = undefined;
	});

	it('should invalidate invalid input', function() {
		demand(List.fields.number.validateInput({
			number: {}
		})).be(false);
		demand(List.fields.number.validateInput({
			number: []
		})).be(false);
		demand(List.fields.number.validateInput({
			number: 'a'
		})).be(false);
	});

	it('should update top level fields', function() {
		List.fields.number.updateItem(testItem, {
			number: 42
		});
		demand(testItem.number).be(42);
		testItem.number = undefined;
	});

	it('should update nested fields', function() {
		List.fields['nested.number'].updateItem(testItem, {
			nested: {
				number: 42
			}
		});
		demand(testItem.nested.number).be(42);
		testItem.nested.number = undefined;
	});

	it('should update nested fields with flat paths', function() {
		List.fields['nested.number'].updateItem(testItem, {
			'nested.number': 42
		});
		demand(testItem.nested.number).be(42);
		testItem.nested.number = undefined;
	});

	it('should null value with empty string', function() {
		testItem.number = 1;
		List.fields.number.updateItem(testItem, {
			number: ''
		});
		demand(testItem.number).be(null);
		testItem.number = undefined;
	});

	it('should convert string values', function() {
		testItem.number = 1;
		List.fields.number.updateItem(testItem, {
			number: '50.50'
		});
		demand(testItem.number).be(50.50);
		testItem.number = undefined;
	});
};
<commit_msg>Adding more Number field tests<commit_after>var demand = require('must'),
	NumberType = require('../NumberType');

exports.initList = function(List) {
	List.add({
		number: { type: NumberType },
		nested: {
			number: { type: NumberType }
		}
	});
};

exports.testFieldType = function(List) {
	var testItem = new List.model();

	it('should validate numeric input', function() {
		demand(List.fields.number.validateInput({
			number: 0
		})).be(true);
		demand(List.fields.number.validateInput({
			number: 1
		})).be(true);
		demand(List.fields.number.validateInput({
			number: -1
		})).be(true);
		demand(List.fields.number.validateInput({
			number: 1.1
		})).be(true);
	});

	it('should validate string input', function() {
		demand(List.fields.number.validateInput({
			number: '0'
		})).be(true);
		demand(List.fields.number.validateInput({
			number: '1'
		})).be(true);
		demand(List.fields.number.validateInput({
			number: '-1'
		})).be(true);
		demand(List.fields.number.validateInput({
			number: '1.1'
		})).be(true);
	});

	it('should validate no input', function() {
		demand(List.fields.number.validateInput({})).be(true);
		demand(List.fields.number.validateInput({}, true)).be(false);
		testItem.number = 1;
		demand(List.fields.number.validateInput({}, true, testItem)).be(true);
		testItem.number = undefined;
	});

	it('should validate empty strings', function() {
		demand(List.fields.number.validateInput({
			number: ''
		})).be(true);
		demand(List.fields.number.validateInput({
			number: ''
		}, true)).be(false);
		testItem.number = 1;
		demand(List.fields.number.validateInput({
			number: ''
		}, true, testItem)).be(true);
		testItem.number = undefined;
	});

	it('should invalidate invalid input', function() {
		demand(List.fields.number.validateInput({
			number: {}
		})).be(false);
		demand(List.fields.number.validateInput({
			number: []
		})).be(false);
		demand(List.fields.number.validateInput({
			number: 'a'
		})).be(false);
	});

	it('should update top level fields', function() {
		List.fields.number.updateItem(testItem, {
			number: 42
		});
		demand(testItem.number).be(42);
		testItem.number = undefined;
	});

	it('should update nested fields', function() {
		List.fields['nested.number'].updateItem(testItem, {
			nested: {
				number: 42
			}
		});
		demand(testItem.nested.number).be(42);
		testItem.nested.number = undefined;
	});

	it('should update nested fields with flat paths', function() {
		List.fields['nested.number'].updateItem(testItem, {
			'nested.number': 42
		});
		demand(testItem.nested.number).be(42);
		testItem.nested.number = undefined;
	});

	it('should null value with empty string', function() {
		testItem.number = 1;
		List.fields.number.updateItem(testItem, {
			number: ''
		});
		demand(testItem.number).be(null);
		testItem.number = undefined;
	});

	it('should null value when undefined', function() {
		testItem.number = 1;
		List.fields.number.updateItem(testItem, {
			number: undefined
		});
		demand(testItem.number).be(null);
		testItem.number = undefined;
	});

	it('should convert string values', function() {
		testItem.number = 1;
		List.fields.number.updateItem(testItem, {
			number: '50.50'
		});
		demand(testItem.number).be(50.50);
		testItem.number = undefined;
	});
};
<|endoftext|>


<commit_before>var Bounds = require('../util/Bounds'),
    canvas = require('../util/canvas'),
    font = require('./font'),
    paths = require('../path'),
    parse = paths.parse,
    boundPath = paths.bounds,
    areaPath = paths.area,
    linePath = paths.line,
    halfpi = Math.PI / 2,
    sqrt3 = Math.sqrt(3),
    tan30 = Math.tan(30 * Math.PI / 180),
    g2D = null;

function context() {
  return g2D || (g2D = canvas.instance(1,1).getContext('2d'));
}

function pathBounds(o, path, bounds) {
  if (path == null) {
    bounds.set(0, 0, 0, 0);
  } else {
    boundPath(path, bounds);
    if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
      bounds.expand(o.strokeWidth);
    }
  }
  return bounds;
}

function path(o, bounds) {
  var p = o.path ? o.pathCache || (o.pathCache = parse(o.path)) : null;
  return pathBounds(o, p, bounds).translate(o.x || 0, o.y || 0);
}

function area(o, bounds) {
  var items = o.mark.items;
  o = items[0];
  var p = o.pathCache || (o.pathCache = parse(areaPath(items)));
  return pathBounds(items[0], p, bounds);
}

function line(o, bounds) {
  var items = o.mark.items;
  o = items[0];
  var p = o.pathCache || (o.pathCache = parse(linePath(items)));
  return pathBounds(items[0], p, bounds);
}

function rect(o, bounds) {
  var x = o.x || 0,
      y = o.y || 0,
      u = (x + o.width) || 0,
      v = (y + o.height) || 0;
  bounds.set(x, y, u, v);
  if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
    bounds.expand(o.strokeWidth);
  }
  return bounds;
}

function image(o, bounds) {
  var x = o.x || 0,
      y = o.y || 0,
      w = o.width || 0,
      h = o.height || 0;
  x = x - (o.align === 'center' ? w/2 : (o.align === 'right' ? w : 0));
  y = y - (o.baseline === 'middle' ? h/2 : (o.baseline === 'bottom' ? h : 0));
  return bounds.set(x, y, x+w, y+h);
}

function rule(o, bounds) {
  var x1, y1;
  bounds.set(
    x1 = o.x || 0,
    y1 = o.y || 0,
    o.x2 != null ? o.x2 : x1,
    o.y2 != null ? o.y2 : y1
  );
  if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
    bounds.expand(o.strokeWidth);
  }
  return bounds;
}

function arc(o, bounds) {
  var cx = o.x || 0,
      cy = o.y || 0,
      ir = o.innerRadius || 0,
      or = o.outerRadius || 0,
      sa = (o.startAngle || 0) - halfpi,
      ea = (o.endAngle || 0) - halfpi,
      xmin = Infinity, xmax = -Infinity,
      ymin = Infinity, ymax = -Infinity,
      a, i, n, x, y, ix, iy, ox, oy;

  var angles = [sa, ea],
      s = sa - (sa % halfpi);
  for (i=0; i<4 && s<ea; ++i, s+=halfpi) {
    angles.push(s);
  }

  for (i=0, n=angles.length; i<n; ++i) {
    a = angles[i];
    x = Math.cos(a); ix = ir*x; ox = or*x;
    y = Math.sin(a); iy = ir*y; oy = or*y;
    xmin = Math.min(xmin, ix, ox);
    xmax = Math.max(xmax, ix, ox);
    ymin = Math.min(ymin, iy, oy);
    ymax = Math.max(ymax, iy, oy);
  }

  bounds.set(cx+xmin, cy+ymin, cx+xmax, cy+ymax);
  if (o.stroke && o.opacity !== 0 && o.strokeWidth !== 0) {
    bounds.expand(o.strokeWidth || 1);
  }
  return bounds;
}

function symbol(o, bounds) {
  var size = o.size != null ? o.size : 100,
      x = o.x || 0,
      y = o.y || 0,
      r, t, rx, ry;

  switch (o.shape) {
    case 'cross':
      r = Math.sqrt(size / 5) / 2;
      t = 3*r;
      bounds.set(x-t, y-r, x+t, y+r);
      break;

    case 'diamond':
      ry = Math.sqrt(size / (2 * tan30));
      rx = ry * tan30;
      bounds.set(x-rx, y-ry, x+rx, y+ry);
      break;

    case 'square':
      t = Math.sqrt(size);
      r = t / 2;
      bounds.set(x-r, y-r, x+r, y+r);
      break;

    case 'triangle-down':
      rx = Math.sqrt(size / sqrt3);
      ry = rx * sqrt3 / 2;
      bounds.set(x-rx, y-ry, x+rx, y+ry);
      break;

    case 'triangle-up':
      rx = Math.sqrt(size / sqrt3);
      ry = rx * sqrt3 / 2;
      bounds.set(x-rx, y-ry, x+rx, y+ry);
      break;

    default:
      r = Math.sqrt(size/Math.PI);
      bounds.set(x-r, y-r, x+r, y+r);
  }
  if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
    bounds.expand(o.strokeWidth);
  }
  return bounds;
}

function text(o, bounds, noRotate) {
  var x = (o.x || 0) + (o.dx || 0),
      y = (o.y || 0) + (o.dy || 0),
      h = font.size(o),
      a = o.align,
      r = o.radius || 0,
      g = context(), w, t;

  g.font = font.string(o);
  w = g.measureText(o.text || '').width;

  if (r) {
    t = (o.theta || 0) - Math.PI/2;
    x += r * Math.cos(t);
    y += r * Math.sin(t);
  }

  // horizontal
  if (a === 'center') {
    x = x - (w / 2);
  } else if (a === 'right') {
    x = x - w;
  } else {
    // left by default, do nothing
  }

  // vertical
  y += font.offset(o) - Math.round(0.8*h);

  bounds.set(x, y, x+w, y+h);
  if (o.angle && !noRotate) {
    bounds.rotate(o.angle*Math.PI/180, o.x||0, o.y||0);
  }
  return bounds.expand(noRotate ? 0 : 1);
}

function group(g, bounds, includeLegends) {
  var axes = g.axisItems || [],
      items = g.items || [],
      legends = g.legendItems || [],
      j, m;

  for (j=0, m=axes.length; j<m; ++j) {
    bounds.union(axes[j].bounds);
  }
  for (j=0, m=items.length; j<m; ++j) {
    bounds.union(items[j].bounds);
  }
  if (includeLegends) {
    for (j=0, m=legends.length; j<m; ++j) {
      bounds.union(legends[j].bounds);
    }
  }
  if (g.width != null && g.height != null) {
    bounds.add(g.width, g.height);
  }
  if (g.x != null && g.y != null) {
    bounds.add(0, 0);
  }
  return bounds.translate(g.x || 0, g.y || 0);
}

var methods = {
  group:  group,
  symbol: symbol,
  image:  image,
  rect:   rect,
  rule:   rule,
  arc:    arc,
  text:   text,
  path:   path,
  area:   area,
  line:   line
};

function itemBounds(item, func, opt) {
  func = func || methods[item.mark.marktype];
  var curr = item.bounds,
      prev = item['bounds:prev'] || (item['bounds:prev'] = new Bounds());

  if (curr) {
    prev.clear().union(curr);
    curr.clear();
  } else {
    item.bounds = new Bounds();
  }
  func(item, item.bounds, opt);
  if (!curr) prev.clear().union(item.bounds);
  return item.bounds;
}

function markBounds(mark, bounds, opt) {
  bounds = bounds || mark.bounds && mark.bounds.clear() || new Bounds();

  var type  = mark.marktype,
      func  = methods[type],
      items = mark.items,
      i, n;

  if ((type==='area' || type==='line') && items.length) {
    bounds.union(itemBounds(items[0], func, opt));
  } else if (items) {
    for (i=0, n=items.length; i<n; ++i) {
      bounds.union(itemBounds(items[i], func, opt));
    }
  }
  return (mark.bounds = bounds);
}

module.exports = {
  mark:  markBounds,
  item:  itemBounds,
  text:  text,
  group: group
};
<commit_msg>Standardize stroke bounds expansion.<commit_after>var Bounds = require('../util/Bounds'),
    canvas = require('../util/canvas'),
    font = require('./font'),
    paths = require('../path'),
    parse = paths.parse,
    boundPath = paths.bounds,
    areaPath = paths.area,
    linePath = paths.line,
    halfpi = Math.PI / 2,
    sqrt3 = Math.sqrt(3),
    tan30 = Math.tan(30 * Math.PI / 180),
    g2D = null;

function context() {
  return g2D || (g2D = canvas.instance(1,1).getContext('2d'));
}

function pathBounds(o, path, bounds) {
  if (path == null) {
    bounds.set(0, 0, 0, 0);
  } else {
    boundPath(path, bounds);
    if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
      bounds.expand(o.strokeWidth);
    }
  }
  return bounds;
}

function strokeBounds(o, bounds) {
  if (o.stroke && o.opacity !== 0 && o.stokeOpacity !== 0) {
    bounds.expand(o.strokeWidth != null ? o.strokeWidth : 1);
  }
  return bounds;
}

function path(o, bounds) {
  var p = o.path ? o.pathCache || (o.pathCache = parse(o.path)) : null;
  return pathBounds(o, p, bounds).translate(o.x || 0, o.y || 0);
}

function area(o, bounds) {
  var items = o.mark.items;
  o = items[0];
  var p = o.pathCache || (o.pathCache = parse(areaPath(items)));
  return pathBounds(items[0], p, bounds);
}

function line(o, bounds) {
  var items = o.mark.items;
  o = items[0];
  var p = o.pathCache || (o.pathCache = parse(linePath(items)));
  return pathBounds(items[0], p, bounds);
}

function rect(o, bounds) {
  var x = o.x || 0,
      y = o.y || 0,
      u = (x + o.width) || 0,
      v = (y + o.height) || 0;
  return strokeBounds(o, bounds.set(x, y, u, v));
}

function image(o, bounds) {
  var x = o.x || 0,
      y = o.y || 0,
      w = o.width || 0,
      h = o.height || 0;
  x = x - (o.align === 'center' ? w/2 : (o.align === 'right' ? w : 0));
  y = y - (o.baseline === 'middle' ? h/2 : (o.baseline === 'bottom' ? h : 0));
  return bounds.set(x, y, x+w, y+h);
}

function rule(o, bounds) {
  var x1, y1;
  bounds.set(
    x1 = o.x || 0,
    y1 = o.y || 0,
    o.x2 != null ? o.x2 : x1,
    o.y2 != null ? o.y2 : y1
  );
  return strokeBounds(o, bounds);
}

function arc(o, bounds) {
  var cx = o.x || 0,
      cy = o.y || 0,
      ir = o.innerRadius || 0,
      or = o.outerRadius || 0,
      sa = (o.startAngle || 0) - halfpi,
      ea = (o.endAngle || 0) - halfpi,
      xmin = Infinity, xmax = -Infinity,
      ymin = Infinity, ymax = -Infinity,
      a, i, n, x, y, ix, iy, ox, oy;

  var angles = [sa, ea],
      s = sa - (sa % halfpi);
  for (i=0; i<4 && s<ea; ++i, s+=halfpi) {
    angles.push(s);
  }

  for (i=0, n=angles.length; i<n; ++i) {
    a = angles[i];
    x = Math.cos(a); ix = ir*x; ox = or*x;
    y = Math.sin(a); iy = ir*y; oy = or*y;
    xmin = Math.min(xmin, ix, ox);
    xmax = Math.max(xmax, ix, ox);
    ymin = Math.min(ymin, iy, oy);
    ymax = Math.max(ymax, iy, oy);
  }

  bounds.set(cx+xmin, cy+ymin, cx+xmax, cy+ymax);
  return strokeBounds(o, bounds);
}

function symbol(o, bounds) {
  var size = o.size != null ? o.size : 100,
      x = o.x || 0,
      y = o.y || 0,
      r, t, rx, ry;

  switch (o.shape) {
    case 'cross':
      r = Math.sqrt(size / 5) / 2;
      t = 3*r;
      bounds.set(x-t, y-r, x+t, y+r);
      break;

    case 'diamond':
      ry = Math.sqrt(size / (2 * tan30));
      rx = ry * tan30;
      bounds.set(x-rx, y-ry, x+rx, y+ry);
      break;

    case 'square':
      t = Math.sqrt(size);
      r = t / 2;
      bounds.set(x-r, y-r, x+r, y+r);
      break;

    case 'triangle-down':
      rx = Math.sqrt(size / sqrt3);
      ry = rx * sqrt3 / 2;
      bounds.set(x-rx, y-ry, x+rx, y+ry);
      break;

    case 'triangle-up':
      rx = Math.sqrt(size / sqrt3);
      ry = rx * sqrt3 / 2;
      bounds.set(x-rx, y-ry, x+rx, y+ry);
      break;

    default:
      r = Math.sqrt(size/Math.PI);
      bounds.set(x-r, y-r, x+r, y+r);
  }
  return strokeBounds(o, bounds);
}

function text(o, bounds, noRotate) {
  var x = (o.x || 0) + (o.dx || 0),
      y = (o.y || 0) + (o.dy || 0),
      h = font.size(o),
      a = o.align,
      r = o.radius || 0,
      g = context(), w, t;

  g.font = font.string(o);
  w = g.measureText(o.text || '').width;

  if (r) {
    t = (o.theta || 0) - Math.PI/2;
    x += r * Math.cos(t);
    y += r * Math.sin(t);
  }

  // horizontal
  if (a === 'center') {
    x = x - (w / 2);
  } else if (a === 'right') {
    x = x - w;
  } else {
    // left by default, do nothing
  }

  // vertical
  y += font.offset(o) - Math.round(0.8*h);

  bounds.set(x, y, x+w, y+h);
  if (o.angle && !noRotate) {
    bounds.rotate(o.angle*Math.PI/180, o.x||0, o.y||0);
  }
  return bounds.expand(noRotate ? 0 : 1);
}

function group(g, bounds, includeLegends) {
  var axes = g.axisItems || [],
      items = g.items || [],
      legends = g.legendItems || [],
      j, m;

  for (j=0, m=axes.length; j<m; ++j) {
    bounds.union(axes[j].bounds);
  }
  for (j=0, m=items.length; j<m; ++j) {
    bounds.union(items[j].bounds);
  }
  if (includeLegends) {
    for (j=0, m=legends.length; j<m; ++j) {
      bounds.union(legends[j].bounds);
    }
  }
  if (g.width != null && g.height != null) {
    bounds.add(g.width, g.height);
  }
  if (g.x != null && g.y != null) {
    bounds.add(0, 0);
  }
  return bounds.translate(g.x || 0, g.y || 0);
}

var methods = {
  group:  group,
  symbol: symbol,
  image:  image,
  rect:   rect,
  rule:   rule,
  arc:    arc,
  text:   text,
  path:   path,
  area:   area,
  line:   line
};

function itemBounds(item, func, opt) {
  func = func || methods[item.mark.marktype];
  var curr = item.bounds,
      prev = item['bounds:prev'] || (item['bounds:prev'] = new Bounds());

  if (curr) {
    prev.clear().union(curr);
    curr.clear();
  } else {
    item.bounds = new Bounds();
  }
  func(item, item.bounds, opt);
  if (!curr) prev.clear().union(item.bounds);
  return item.bounds;
}

function markBounds(mark, bounds, opt) {
  bounds = bounds || mark.bounds && mark.bounds.clear() || new Bounds();

  var type  = mark.marktype,
      func  = methods[type],
      items = mark.items,
      i, n;

  if ((type==='area' || type==='line') && items.length) {
    bounds.union(itemBounds(items[0], func, opt));
  } else if (items) {
    for (i=0, n=items.length; i<n; ++i) {
      bounds.union(itemBounds(items[i], func, opt));
    }
  }
  return (mark.bounds = bounds);
}

module.exports = {
  mark:  markBounds,
  item:  itemBounds,
  text:  text,
  group: group
};
<|endoftext|>


<commit_before>'use strict';

var nodemailer = require('nodemailer');
var sgTransport = require('nodemailer-sendgrid-transport');
var mandrillTransport = require('nodemailer-mandrill-transport');
var mailgunTransport = require('nodemailer-mailgun-transport');
var nunjucks = require('./nunjucks');
var jwt = require('jsonwebtoken');
var debug = require('debug')('formio:settings:email');
var _ = require('lodash');

/**
 * The email sender for emails.
 * @param formio
 * @returns {{send: Function}}
 */
module.exports = function(formio) {
  var hook = require('./hook')(formio);
  return {
    availableTransports: function(req, next) {
      hook.settings(req, function(err, settings) {
        if (err) {
          return next(err);
        }

        var availableTransports = [
          {
            transport: 'default',
            title: 'Default (charges may apply)'
          }
        ];
        if (_.get(settings, 'email.gmail.auth.user')
          && _.get(settings, 'email.gmail.auth.pass')) {
          availableTransports.push(
            {
              transport: 'gmail',
              title: 'G-Mail'
            }
          );
        }
        if (_.get(settings, 'email.sendgrid.auth.api_user') && _.get(settings, 'email.sendgrid.auth.api_key')
          || (!_.get(settings, 'email.sendgrid.auth.api_user') && _.get(settings, 'email.sendgrid.auth.api_key'))
        ) {
          // Omit the username if user has configured sendgrid for api key access.
          if (_.get(settings, 'email.sendgrid.auth.api_user') === 'apikey') {
            settings.email.sendgrid.auth = _.omit(settings.email.sendgrid.auth, 'api_user');
          }
          availableTransports.push(
            {
              transport: 'sendgrid',
              title: 'SendGrid'
            }
          );
        }
        if (_.get(settings, 'email.mandrill.auth.apiKey')) {
          availableTransports.push(
            {
              transport: 'mandrill',
              title: 'Mandrill'
            }
          );
        }
        if (_.get(settings, 'email.mailgun.auth.api_key')) {
          availableTransports.push(
            {
              transport: 'mailgun',
              title: 'Mailgun'
            }
          );
        }

        availableTransports = hook.alter('emailTransports', availableTransports, settings);
        next(null, availableTransports);
      });
    },
    settings: function(transport, from, emails, subject, message) {
      return {
        transport: transport,
        from: from,
        emails: emails,
        subject: subject,
        message: message
      };
    },
    send: function(req, res, message, params, next) {
      // The transporter object.
      var transporter = null;

      // Add the request params.
      params.req = {
        user: req.user,
        token: req.token,
        params: req.params,
        query: req.query
      };

      // Add the response parameters.
      params.res = {
        token: res.token
      };

      var getMail = function(cb) {
        try {
          var emails = (typeof message.emails === 'string') ? message.emails : message.emails.join(', ');
          // Allow disabling of sending emails to external email accounts.
          if (process.env.EMAIL_OVERRIDE) {
            emails = process.env.EMAIL_OVERRIDE;
          }

          // Render the mail objects
          var mail = nunjucks.renderObj({
            from: message.from ? message.from : 'no-reply@form.io',
            to: emails,
            subject: message.subject,
            html: message.message
          }, params);

          // Allow others to alter the email.
          hook.alter('email', mail, req, res, params, cb);
        }
        catch (e) {
          cb();
        }
      };

      // To send the mail.
      var sendMail = function(err, mail) {
        if (err) {
          return next(err);
        }
        if (transporter && mail) {
          transporter.sendMail(mail);
          return next();
        }
        else {
          debug('No transport configured, dont send an email.');
          return next();
        }
      };

      // Get the settings.
      hook.settings(req, function(err, settings) {
        if (err) {
          return;
        }

        var emailType = message.transport ? message.transport : 'default';
        var _config = (formio && formio.config && formio.config.email && formio.config.email.type);

        debug(formio.config);
        debug(emailType);
        switch (emailType) {
          case 'default':
            if (_config && formio.config.email.type === 'sendgrid') {
              /* eslint-disable camelcase */
              // Check if the user has configured sendgrid for api key access.
              if (!formio.config.email.username && formio.config.email.password) {
                transporter = nodemailer.createTransport(sgTransport({
                  auth: {
                    api_key: formio.config.email.password
                  }
                }));
              }
              else {
                transporter = nodemailer.createTransport(sgTransport({
                  auth: {
                    api_user: formio.config.email.username,
                    api_key: formio.config.email.password
                  }
                }));
              }
              /* eslint-enable camelcase */
            }
            else if (_config && formio.config.email.type === 'mandrill') {
              transporter = nodemailer.createTransport(mandrillTransport({
                auth: {
                  apiKey: formio.config.email.apiKey
                }
              }));
            }
            getMail(sendMail);
            break;
          case 'sendgrid':
            debug(settings.email.sendgrid);
            if (settings.email.sendgrid) {
              // Check if the user has configured sendgrid for api key access.
              if (_.get(settings, 'email.sendgrid.auth.api_user')
                && _.get(settings, 'email.sendgrid.auth.api_user').toString() === 'apikey'
              ) {
                settings.email.sendgrid.auth = _.omit(settings.email.sendgrid.auth, 'api_user');
              }

              debug(settings.email.sendgrid);
              transporter = nodemailer.createTransport(sgTransport(settings.email.sendgrid));
              getMail(sendMail);
            }
            break;
          case 'mandrill':
            if (settings.email.mandrill) {
              transporter = nodemailer.createTransport(mandrillTransport(settings.email.mandrill));
              getMail(sendMail);
            }
            break;
          case 'mailgun':
            if (settings.email.mailgun) {
              transporter = nodemailer.createTransport(mailgunTransport(settings.email.mailgun));
              getMail(sendMail);
            }
            break;
          case 'gmail':
            if (settings.email.gmail) {
              var options = settings.email.gmail;
              options.service = 'Gmail';
              transporter = nodemailer.createTransport(options);
              getMail(sendMail);
            }
            break;
          case 'test':
            getMail(function(err, mail) {
              if (err) {
                return next(err);
              }
              hook.invoke('email', emailType, mail);
              return next();
            });
            break;
          default:
            hook.invoke('email', emailType, message, settings, req, res, params);
            return next();
        }
      });
    }
  };
};
<commit_msg>Ensure mail is sent regardless of error.<commit_after>'use strict';

var nodemailer = require('nodemailer');
var sgTransport = require('nodemailer-sendgrid-transport');
var mandrillTransport = require('nodemailer-mandrill-transport');
var mailgunTransport = require('nodemailer-mailgun-transport');
var nunjucks = require('./nunjucks');
var jwt = require('jsonwebtoken');
var debug = require('debug')('formio:settings:email');
var _ = require('lodash');

/**
 * The email sender for emails.
 * @param formio
 * @returns {{send: Function}}
 */
module.exports = function(formio) {
  var hook = require('./hook')(formio);
  return {
    availableTransports: function(req, next) {
      hook.settings(req, function(err, settings) {
        if (err) {
          return next(err);
        }

        var availableTransports = [
          {
            transport: 'default',
            title: 'Default (charges may apply)'
          }
        ];
        if (_.get(settings, 'email.gmail.auth.user')
          && _.get(settings, 'email.gmail.auth.pass')) {
          availableTransports.push(
            {
              transport: 'gmail',
              title: 'G-Mail'
            }
          );
        }
        if (_.get(settings, 'email.sendgrid.auth.api_user') && _.get(settings, 'email.sendgrid.auth.api_key')
          || (!_.get(settings, 'email.sendgrid.auth.api_user') && _.get(settings, 'email.sendgrid.auth.api_key'))
        ) {
          // Omit the username if user has configured sendgrid for api key access.
          if (_.get(settings, 'email.sendgrid.auth.api_user') === 'apikey') {
            settings.email.sendgrid.auth = _.omit(settings.email.sendgrid.auth, 'api_user');
          }
          availableTransports.push(
            {
              transport: 'sendgrid',
              title: 'SendGrid'
            }
          );
        }
        if (_.get(settings, 'email.mandrill.auth.apiKey')) {
          availableTransports.push(
            {
              transport: 'mandrill',
              title: 'Mandrill'
            }
          );
        }
        if (_.get(settings, 'email.mailgun.auth.api_key')) {
          availableTransports.push(
            {
              transport: 'mailgun',
              title: 'Mailgun'
            }
          );
        }

        availableTransports = hook.alter('emailTransports', availableTransports, settings);
        next(null, availableTransports);
      });
    },
    settings: function(transport, from, emails, subject, message) {
      return {
        transport: transport,
        from: from,
        emails: emails,
        subject: subject,
        message: message
      };
    },
    send: function(req, res, message, params, next) {
      // The transporter object.
      var transporter = null;

      // Add the request params.
      params.req = {
        user: req.user,
        token: req.token,
        params: req.params,
        query: req.query
      };

      // Add the response parameters.
      params.res = {
        token: res.token
      };

      var getMail = function(cb) {
        var mail = {};
        try {
          var emails = (typeof message.emails === 'string') ? message.emails : message.emails.join(', ');
          // Allow disabling of sending emails to external email accounts.
          if (process.env.EMAIL_OVERRIDE) {
            emails = process.env.EMAIL_OVERRIDE;
          }

          // Render the mail objects
          mail = nunjucks.renderObj({
            from: message.from ? message.from : 'no-reply@form.io',
            to: emails,
            subject: message.subject,
            html: message.message
          }, params);

          // Allow others to alter the email.
          hook.alter('email', mail, req, res, params, cb);
        }
        catch (e) {
          cb(null, mail);
        }
      };

      // To send the mail.
      var sendMail = function(err, mail) {
        if (err) {
          return next(err);
        }
        if (transporter && mail) {
          transporter.sendMail(mail);
          return next();
        }
        else {
          debug('No transport configured, dont send an email.');
          return next();
        }
      };

      // Get the settings.
      hook.settings(req, function(err, settings) {
        if (err) {
          return;
        }

        var emailType = message.transport ? message.transport : 'default';
        var _config = (formio && formio.config && formio.config.email && formio.config.email.type);

        debug(formio.config);
        debug(emailType);
        switch (emailType) {
          case 'default':
            if (_config && formio.config.email.type === 'sendgrid') {
              /* eslint-disable camelcase */
              // Check if the user has configured sendgrid for api key access.
              if (!formio.config.email.username && formio.config.email.password) {
                transporter = nodemailer.createTransport(sgTransport({
                  auth: {
                    api_key: formio.config.email.password
                  }
                }));
              }
              else {
                transporter = nodemailer.createTransport(sgTransport({
                  auth: {
                    api_user: formio.config.email.username,
                    api_key: formio.config.email.password
                  }
                }));
              }
              /* eslint-enable camelcase */
            }
            else if (_config && formio.config.email.type === 'mandrill') {
              transporter = nodemailer.createTransport(mandrillTransport({
                auth: {
                  apiKey: formio.config.email.apiKey
                }
              }));
            }
            getMail(sendMail);
            break;
          case 'sendgrid':
            debug(settings.email.sendgrid);
            if (settings.email.sendgrid) {
              // Check if the user has configured sendgrid for api key access.
              if (_.get(settings, 'email.sendgrid.auth.api_user')
                && _.get(settings, 'email.sendgrid.auth.api_user').toString() === 'apikey'
              ) {
                settings.email.sendgrid.auth = _.omit(settings.email.sendgrid.auth, 'api_user');
              }

              debug(settings.email.sendgrid);
              transporter = nodemailer.createTransport(sgTransport(settings.email.sendgrid));
              getMail(sendMail);
            }
            break;
          case 'mandrill':
            if (settings.email.mandrill) {
              transporter = nodemailer.createTransport(mandrillTransport(settings.email.mandrill));
              getMail(sendMail);
            }
            break;
          case 'mailgun':
            if (settings.email.mailgun) {
              transporter = nodemailer.createTransport(mailgunTransport(settings.email.mailgun));
              getMail(sendMail);
            }
            break;
          case 'gmail':
            if (settings.email.gmail) {
              var options = settings.email.gmail;
              options.service = 'Gmail';
              transporter = nodemailer.createTransport(options);
              getMail(sendMail);
            }
            break;
          case 'test':
            getMail(function(err, mail) {
              if (err) {
                return next(err);
              }
              hook.invoke('email', emailType, mail);
              return next();
            });
            break;
          default:
            hook.invoke('email', emailType, message, settings, req, res, params);
            return next();
        }
      });
    }
  };
};
<|endoftext|>


<commit_before>require('@jymfony/autoloader');

const Debug = Jymfony.Component.Debug.Debug;
const Runner = Jymfony.Component.Testing.Framework.Runner;
Debug.enable();

const [ , , ...argv ] = [ ...process.argv ];
if (0 === argv.length) {
    argv.push('test/**/*.js');
}

process.argv = argv;
new Runner().run();
<commit_msg>[util] fix(tests): fixed execution of standalone component<commit_after>require('./index');
require('@jymfony/autoloader');

const Debug = Jymfony.Component.Debug.Debug;
const Runner = Jymfony.Component.Testing.Framework.Runner;
Debug.enable();

const [ , , ...argv ] = [ ...process.argv ];
if (0 === argv.length) {
    argv.push('test/**/*.js');
}

process.argv = argv;
new Runner().run();
<|endoftext|>


<commit_before>window.ls.container.get('view').add({
    selector: 'data-ls-bind',
    controller: function(element, expression, container, $prefix, $as) {
        let echo = function(value, bind = true) {
            if(
                element.tagName === 'INPUT' ||
                element.tagName === 'SELECT' ||
                element.tagName === 'BUTTON' ||
                element.tagName === 'TEXTAREA'
            ) {
                let type = element.getAttribute('type');

                if ('radio' === type) {
                    if (value.toString() === element.value) {
                        element.setAttribute('checked', 'checked');
                    }
                    else {
                        element.removeAttribute('checked');
                    }

                    if(bind) {
                        element.addEventListener('change', () => {
                            for(let i = 0; i < paths.length; i++) {
                                if(element.checked) {
                                    value = element.value;
                                }

                                container.path(paths[i], value, $as, $prefix);
                            }
                        });
                    }

                    return;
                }

                if('checkbox' === type) {
                    if(typeof value === 'boolean' || value === 'true' || value === 'false') {
                        if(value === true || value === 'true') {
                            element.setAttribute('checked', 'checked');
                            element.checked = true;
                        }
                        else {
                            element.removeAttribute('checked');
                            element.checked = false;
                        }
                    }
                    else {
                        try {
                            value = JSON.parse(value);

                            element.checked = (Array.isArray(value) && (value.indexOf(element.value) > -1));

                            value = element.value;
                        }
                        catch {
                            return null;
                        }
                    }

                    if(bind) {
                        element.addEventListener('change', () => {
                            for(let i = 0; i < paths.length; i++) {
                                let value = container.path(paths[i], undefined, $as, $prefix);
                                let index = value.indexOf(element.value);

                                if(element.checked  && index < 0) {
                                    value.push(element.value);
                                }

                                if(!element.checked && index > -1) {
                                    value.splice(index, 1);
                                }

                                container.path(paths[i], value, $as, $prefix);
                            }
                        });
                    }

                    return;
                }

                if (element.value !== value) {
                    element.value = value;
                }

                if(bind) {
                    element.addEventListener('input', sync);
                    element.addEventListener('change', sync);
                }
            }
            else {
                if(element.innerText !== value) {
                    element.innerHTML = value;
                }
            }
        };
        let sync = ((as, prefix) => {
            return () => {
                for(let i = 0; i < paths.length; i++) {
                    container.path(paths[i], element.value, as, prefix);
                }
            }
        })($as, $prefix);

        let syntax = element.getAttribute('data-ls-bind');
        let result = expression.parse(syntax, null, $as, $prefix);
        let paths  = expression.getPaths();

        echo(result, true);

        element.addEventListener('looped', function () { // Rebind after loop comp finish to render
            echo(expression.parse(syntax, null, $as, $prefix), false);
        });

        for(let i = 0; i < paths.length; i++) {
            let path = paths[i].split('.');

            while(path.length) {
                container.bind(element, path.join('.'), () => {
                    echo(expression.parse(syntax, null, $as, $prefix), false);
                });
                path.pop();
            }
        }
    }
});
<commit_msg>Added change event on bind<commit_after>window.ls.container.get('view').add({
    selector: 'data-ls-bind',
    controller: function(element, expression, container, $prefix, $as) {
        let echo = function(value, bind = true) {
            if(
                element.tagName === 'INPUT' ||
                element.tagName === 'SELECT' ||
                element.tagName === 'BUTTON' ||
                element.tagName === 'TEXTAREA'
            ) {
                let type = element.getAttribute('type');

                if ('radio' === type) {
                    if (value.toString() === element.value) {
                        element.setAttribute('checked', 'checked');
                    }
                    else {
                        element.removeAttribute('checked');
                    }

                    if(bind) {
                        element.addEventListener('change', () => {
                            for(let i = 0; i < paths.length; i++) {
                                if(element.checked) {
                                    value = element.value;
                                }

                                container.path(paths[i], value, $as, $prefix);
                            }
                        });
                    }

                    return;
                }

                if('checkbox' === type) {
                    if(typeof value === 'boolean' || value === 'true' || value === 'false') {
                        if(value === true || value === 'true') {
                            element.setAttribute('checked', 'checked');
                            element.checked = true;
                        }
                        else {
                            element.removeAttribute('checked');
                            element.checked = false;
                        }
                    }
                    else {
                        try {
                            value = JSON.parse(value);

                            element.checked = (Array.isArray(value) && (value.indexOf(element.value) > -1));

                            value = element.value;
                        }
                        catch {
                            return null;
                        }
                    }

                    if(bind) {
                        element.addEventListener('change', () => {
                            for(let i = 0; i < paths.length; i++) {
                                let value = container.path(paths[i], undefined, $as, $prefix);
                                let index = value.indexOf(element.value);

                                if(element.checked  && index < 0) {
                                    value.push(element.value);
                                }

                                if(!element.checked && index > -1) {
                                    value.splice(index, 1);
                                }

                                container.path(paths[i], value, $as, $prefix);
                            }
                        });
                    }

                    return;
                }

                if (element.value !== value) {
                    element.value = value;
                    element.dispatchEvent(new Event('change'));
                }

                if(bind) {
                    element.addEventListener('input', sync);
                    element.addEventListener('change', sync);
                }
            }
            else {
                if(element.innerText !== value) {
                    element.innerHTML = value;
                }
            }
        };
        let sync = ((as, prefix) => {
            return () => {
                for(let i = 0; i < paths.length; i++) {
                    container.path(paths[i], element.value, as, prefix);
                }
            }
        })($as, $prefix);

        let syntax = element.getAttribute('data-ls-bind');
        let unsync = (!!element.getAttribute('data-unsync')) || false;
        let result = expression.parse(syntax, null, $as, $prefix);
        let paths  = expression.getPaths();

        console.log('bind unsync', unsync, !unsync);

        echo(result, !unsync);

        element.addEventListener('looped', function () { // Rebind after loop comp finish to render
            echo(expression.parse(syntax, null, $as, $prefix), false);
        });

        for(let i = 0; i < paths.length; i++) {
            let path = paths[i].split('.');

            while(path.length) {
                container.bind(element, path.join('.'), () => {
                    echo(expression.parse(syntax, null, $as, $prefix), false);
                });
                path.pop();
            }
        }
    }
});
<|endoftext|>