<commit_before>/**
 * background.js
 */
"use strict";
{
  /* api */
  const {browserAction, contextMenus, i18n, runtime, tabs, windows} = browser;
  const storage = browser.storage.local;

  /* constants */
  const CONTENT_GET = "getContent";
  const CONTEXT_MENU = "contextMenu";
  const EDITOR_CMD_ARGS = "editorCmdArgs";
  const EDITOR_CONFIG = "editorConfigPath";
  const EDITOR_CONFIG_GET = "getEditorConfig";
  const EDITOR_CONFIG_RES = "resEditorConfig";
  const EDITOR_CONFIG_SET = "setEditorConfig";
  const EDITOR_CONFIG_TS = "editorConfigTimestamp";
  const EDITOR_FILE_NAME = "editorFileName";
  const EDITOR_FILE_POS = "editorFileAfterCmdArgs";
  const EDITOR_LABEL = "editorLabel";
  const EDITOR_PATH = "editorPath";
  const ENABLE_PB = "enablePB";
  const EXT_WEBEXT = "jid1-WiAigu4HIo0Tag@jetpack";
  const FILE_EXT = "fileExt";
  const FILE_EXT_PATH = "../data/fileExt.json";
  const HOST = "withexeditorhost";
  const ICON = "./img/icon.svg";
  const ICON_COLOR = "buttonIcon";
  const ICON_GRAY = "buttonIconGray";
  const ICON_PATH = "iconPath";
  const ICON_WHITE = "buttonIconWhite";
  const IS_ENABLED = "isEnabled";
  const IS_EXECUTABLE = "isExecutable";
  const IS_WEBEXT = "isWebExtension";
  const KEY_ACCESS = "accessKey";
  const KEY_EDITOR = "editorShortCut";
  const KEY_OPTIONS = "optionsShortCut";
  const LABEL = "withExEditor";
  const LOCAL_FILE_VIEW = "viewLocalFile";
  const MENU_ENABLED = "menuEnabled";
  const MODE_EDIT = "modeEditText";
  const MODE_MATHML = "modeViewMathML";
  const MODE_SELECTION = "modeViewSelection";
  const MODE_SOURCE = "modeViewSource";
  const MODE_SVG = "modeViewSVG";
  const NS_URI = "nsUri";
  const NS_URI_PATH = "../data/nsUri.json";
  const ONLY_EDITABLE = "enableOnlyEditable";
  const OPTIONS_OPEN = "openOptions";
  const PORT_CONTENT = "portContent";
  const PROCESS_CHILD = "childProcess";
  const STORAGE_SET = "setStorage";
  const TEXT_SYNC = "syncText";
  const TMP_FILES_PB_REMOVE = "removePrivateTmpFiles";
  const TMP_FILE_CREATE = "createTmpFile";
  const TMP_FILE_DATA_PORT = "portTmpFileData";
  const TMP_FILE_GET = "getTmpFile";
  const TMP_FILE_RES = "resTmpFile";
  const WARN_COLOR = "#C13832";
  const WARN_TEXT = "!";
  const VARS_SET = "setVars";

  /* variables */
  const vars = {
    [IS_ENABLED]: false,
    [IS_WEBEXT]: runtime.id === EXT_WEBEXT,
    [KEY_ACCESS]: "u",
    [KEY_EDITOR]: true,
    [KEY_OPTIONS]: true,
    [ONLY_EDITABLE]: false,
  };

  const varsL = {
    [EDITOR_LABEL]: "",
    [ENABLE_PB]: false,
    [ICON_PATH]: `${ICON}#gray`,
    [IS_EXECUTABLE]: false,
    [MENU_ENABLED]: false,
    [MODE_MATHML]: "",
    [MODE_SOURCE]: "",
    [MODE_SVG]: "",
  };

  /**
   * log error
   * @param {!Object} e - Error
   * @returns {boolean} - false
   */
  const logError = e => {
    console.error(e);
    return false;
  };

  /**
   * log warn
   * @param {*} msg - message
   * @returns {boolean} - false
   */
  const logWarn = msg => {
    msg && console.warn(msg);
    return false;
  };

  /**
   * log message
   * @param {*} msg - message
   * @returns {Object} - message
   */
  const logMsg = msg => {
    msg && console.log(msg);
    return msg;
  };

  /**
   * is string
   * @param {*} o - object to check
   * @returns {boolean} - result
   */
  const isString = o => typeof o === "string" || o instanceof String;

  /**
   * stringify positive integer
   * @param {number} i - integer
   * @param {boolean} zero - treat 0 as a positive integer
   * @returns {?string} - stringified integer
   */
  const stringifyPositiveInt = (i, zero = false) =>
    Number.isSafeInteger(i) && (zero && i >= 0 || i > 0) && `${i}` || null;

  /* windows */
  /**
   * check if any of windows is incognito
   * @returns {boolean} - result
   */
  const checkWindowIncognito = async () => {
    const windowIds = await windows.getAll({windowTypes: ["normal"]});
    let incog;
    if (windowIds && windowIds.length) {
      for (const windowId of windowIds) {
        incog = windowId.incognito;
        if (incog) {
          break;
        }
      }
    }
    return incog || false;
  };

  /* port */
  /* native application host */
  const host = runtime.connectNative(HOST);

  /**
   * port message to host
   * @param {*} msg - message
   * @returns {void}
   */
  const portHostMsg = async msg => {
    msg && host && host.postMessage(msg);
  };

  /* storage */
  /**
   * set storage
   * @param {Object} obj - object to store
   * @returns {?AsyncFunction} - store object
   */
  const setStorage = async obj => obj && storage.set(obj) || null;

  /**
   * fetch shared data and store
   * @param {string} path - data path
   * @param {string} key - storage key
   * @returns {?AsyncFunction} - set storage
   */
  const storeFetchedData = async (path, key) => {
    const data = isString(path) && isString(key) &&
                   await fetch(path).then(res => res && res.json());
    return data && setStorage({[key]: data}) || null;
  };

  /**
   * port editor config path
   * @returns {AsyncFunction} - port host message
   */
  const portEditorConfigPath = async () => {
    const data = await storage.get(EDITOR_CONFIG);
    const filePath = data && data[EDITOR_CONFIG] &&
                     data[EDITOR_CONFIG].value || "";
    return portHostMsg({[EDITOR_CONFIG_GET]: filePath});
  };

  /* content ports collection */
  const ports = {};

  /**
   * restore ports collection
   * @param {Object} data - disconnected port data
   * @returns {?AsyncFunction} - restore ports (recursive)
   */
  const restorePorts = async (data = {}) => {
    const {tabId, windowId} = data;
    let func;
    if (windowId && tabId && ports[windowId]) {
      delete ports[windowId][tabId];
      Object.keys(ports[windowId]).length === 0 &&
        (func = restorePorts({windowId}));
    } else {
      windowId && delete ports[windowId];
    }
    return func || null;
  };

  /**
   * remove port from ports collection
   * @param {!Object} port - removed port
   * @returns {void}
   */
  const removePort = async (port = {}) => {
    const {sender} = port;
    if (sender) {
      const {tab, url} = sender;
      if (tab) {
        let {windowId, id: tabId} = tab;
        tabId = stringifyPositiveInt(tabId, true);
        windowId = stringifyPositiveInt(windowId, true);
        tabId && windowId && url && ports[windowId] && ports[windowId][tabId] &&
          delete ports[windowId][tabId][url];
      }
    }
  };

  /**
   * port message
   * @param {*} msg - message
   * @param {string} windowId - windowId
   * @param {string} tabId - tabId
   * @returns {void}
   */
  const portMsg = async (msg, windowId, tabId) => {
    if (msg) {
      if (windowId && tabId) {
        const frameUrls = ports[windowId] && ports[windowId][tabId] &&
                            Object.keys(ports[windowId][tabId]);
        if (frameUrls && frameUrls.length) {
          for (const frameUrl of frameUrls) {
            const port = ports[windowId][tabId][frameUrl];
            try {
              port && port.postMessage(msg);
            } catch (e) {
              delete ports[windowId][tabId][frameUrl];
            }
          }
        }
      } else if (windowId) {
        const tabIds = ports[windowId] && Object.keys(ports[windowId]);
        if (tabIds && tabIds.length) {
          for (tabId of tabIds) {
            portMsg(msg, windowId, tabId);
          }
        }
      } else {
        const windowIds = Object.keys(ports);
        if (windowIds.length) {
          for (windowId of windowIds) {
            portMsg(msg, windowId);
          }
        }
      }
    }
  };

  /**
   * port context menu data
   * @param {!Object} info - contextMenus.OnClickData
   * @param {!Object} tab - tabs.Tab
   * @returns {void}
   */
  const portContextMenuData = async (info, tab) => {
    const {frameUrl, pageUrl} = info;
    let {windowId, id: tabId} = tab;
    windowId = stringifyPositiveInt(windowId, true);
    tabId = stringifyPositiveInt(tabId, true);
    if (windowId && tabId) {
      const port = ports[windowId] && ports[windowId][tabId] &&
                   ports[windowId][tabId][frameUrl] ||
                   ports[windowId][tabId][pageUrl];
      port && port.postMessage({
        [CONTENT_GET]: {info, tab},
      });
    }
  };

  /**
   * port sync text
   * @param {*} msg - message
   * @returns {AsyncFunction} - port message
   */
  const portSyncText = async msg => {
    let func;
    if (msg) {
      const {data} = msg;
      if (data) {
        const {tabId, windowId} = data;
        func = portMsg({[TEXT_SYNC]: msg}, windowId, tabId);
      }
    }
    return func || null;
  };

  /* icon */
  /**
   * replace icon
   * @param {string} path - icon path
   * @returns {AsyncFunction} - set icon
   */
  const replaceIcon = async (path = varsL[ICON_PATH]) =>
    browserAction.setIcon({path});

  /**
   * toggle badge
   * @param {boolean} executable - executable
   * @returns {Promise.<Array>} - results of each handler
   */
  const toggleBadge = async (executable = varsL[IS_EXECUTABLE]) => {
    const color = !executable && WARN_COLOR || [0, 0, 0, 0];
    const text = !executable && WARN_TEXT || "";
    return Promise.all([
      browserAction.setBadgeBackgroundColor({color}),
      browserAction.setBadgeText({text}),
    ]);
  };

  /* context menu */
  /* context menu items collection */
  const menus = {
    [MODE_SOURCE]: null,
    [MODE_SELECTION]: null,
    [MODE_EDIT]: null,
  };

  // TODO: implement accesskey, Issue #18
  /**
   * create context menu item
   * @param {string} id - menu item ID
   * @param {Array} contexts - contexts
   * @returns {void}
   */
  const createMenuItem = async (id, contexts) => {
    const label = varsL[EDITOR_LABEL] || LABEL;
    const accKey = !vars[IS_WEBEXT] && vars[KEY_ACCESS] &&
                   `(&${vars[KEY_ACCESS].toUpperCase()})` || "";
    isString(id) && menus.hasOwnProperty(id) && Array.isArray(contexts) && (
      menus[id] = contextMenus.create({
        id, contexts,
        title: i18n.getMessage(id, [label, accKey]),
        enabled: !!varsL[MENU_ENABLED],
      })
    );
  };

  /**
   * create context menu items
   * @returns {Promise.<Array>} - results of each handler
   */
  const createMenuItems = async () => {
    const win = await windows.getCurrent({windowTypes: ["normal"]});
    const enabled = win && (
      !win.incognito || varsL[ENABLE_PB]
    ) || false;
    const bool = enabled && !vars[ONLY_EDITABLE];
    const items = Object.keys(menus);
    const func = [];
    for (const item of items) {
      menus[item] = null;
      switch (item) {
        case MODE_EDIT:
          enabled && func.push(createMenuItem(item, ["editable"]));
          break;
        case MODE_SELECTION:
          bool && func.push(createMenuItem(item, ["selection"]));
          break;
        case MODE_SOURCE:
          bool && func.push(createMenuItem(item, ["frame", "page"]));
          break;
        default:
      }
    }
    return Promise.all(func);
  };

  /**
   * restore context menu
   * @returns {AsyncFunction} - create menu items
   */
  const restoreContextMenu = async () =>
    contextMenus.removeAll().then(createMenuItems);

  // FIXME: sometimes, update does not make it in time, Issue #20
  /**
   * update context menu
   * @param {Object} type - context type data
   * @returns {void}
   */
  const updateContextMenu = async type => {
    if (type) {
      const items = Object.keys(type);
      if (items.length) {
        for (const item of items) {
          const obj = type[item];
          const {menuItemId} = obj;
          if (menus[menuItemId]) {
            if (item === MODE_SOURCE) {<commit_msg>Remove comment<commit_after>/**
 * background.js
 */
"use strict";
{
  /* api */
  const {browserAction, contextMenus, i18n, runtime, tabs, windows} = browser;
  const storage = browser.storage.local;

  /* constants */
  const CONTENT_GET = "getContent";
  const CONTEXT_MENU = "contextMenu";
  const EDITOR_CMD_ARGS = "editorCmdArgs";
  const EDITOR_CONFIG = "editorConfigPath";
  const EDITOR_CONFIG_GET = "getEditorConfig";
  const EDITOR_CONFIG_RES = "resEditorConfig";
  const EDITOR_CONFIG_SET = "setEditorConfig";
  const EDITOR_CONFIG_TS = "editorConfigTimestamp";
  const EDITOR_FILE_NAME = "editorFileName";
  const EDITOR_FILE_POS = "editorFileAfterCmdArgs";
  const EDITOR_LABEL = "editorLabel";
  const EDITOR_PATH = "editorPath";
  const ENABLE_PB = "enablePB";
  const EXT_WEBEXT = "jid1-WiAigu4HIo0Tag@jetpack";
  const FILE_EXT = "fileExt";
  const FILE_EXT_PATH = "../data/fileExt.json";
  const HOST = "withexeditorhost";
  const ICON = "./img/icon.svg";
  const ICON_COLOR = "buttonIcon";
  const ICON_GRAY = "buttonIconGray";
  const ICON_PATH = "iconPath";
  const ICON_WHITE = "buttonIconWhite";
  const IS_ENABLED = "isEnabled";
  const IS_EXECUTABLE = "isExecutable";
  const IS_WEBEXT = "isWebExtension";
  const KEY_ACCESS = "accessKey";
  const KEY_EDITOR = "editorShortCut";
  const KEY_OPTIONS = "optionsShortCut";
  const LABEL = "withExEditor";
  const LOCAL_FILE_VIEW = "viewLocalFile";
  const MENU_ENABLED = "menuEnabled";
  const MODE_EDIT = "modeEditText";
  const MODE_MATHML = "modeViewMathML";
  const MODE_SELECTION = "modeViewSelection";
  const MODE_SOURCE = "modeViewSource";
  const MODE_SVG = "modeViewSVG";
  const NS_URI = "nsUri";
  const NS_URI_PATH = "../data/nsUri.json";
  const ONLY_EDITABLE = "enableOnlyEditable";
  const OPTIONS_OPEN = "openOptions";
  const PORT_CONTENT = "portContent";
  const PROCESS_CHILD = "childProcess";
  const STORAGE_SET = "setStorage";
  const TEXT_SYNC = "syncText";
  const TMP_FILES_PB_REMOVE = "removePrivateTmpFiles";
  const TMP_FILE_CREATE = "createTmpFile";
  const TMP_FILE_DATA_PORT = "portTmpFileData";
  const TMP_FILE_GET = "getTmpFile";
  const TMP_FILE_RES = "resTmpFile";
  const WARN_COLOR = "#C13832";
  const WARN_TEXT = "!";
  const VARS_SET = "setVars";

  /* variables */
  const vars = {
    [IS_ENABLED]: false,
    [IS_WEBEXT]: runtime.id === EXT_WEBEXT,
    [KEY_ACCESS]: "u",
    [KEY_EDITOR]: true,
    [KEY_OPTIONS]: true,
    [ONLY_EDITABLE]: false,
  };

  const varsL = {
    [EDITOR_LABEL]: "",
    [ENABLE_PB]: false,
    [ICON_PATH]: `${ICON}#gray`,
    [IS_EXECUTABLE]: false,
    [MENU_ENABLED]: false,
    [MODE_MATHML]: "",
    [MODE_SOURCE]: "",
    [MODE_SVG]: "",
  };

  /**
   * log error
   * @param {!Object} e - Error
   * @returns {boolean} - false
   */
  const logError = e => {
    console.error(e);
    return false;
  };

  /**
   * log warn
   * @param {*} msg - message
   * @returns {boolean} - false
   */
  const logWarn = msg => {
    msg && console.warn(msg);
    return false;
  };

  /**
   * log message
   * @param {*} msg - message
   * @returns {Object} - message
   */
  const logMsg = msg => {
    msg && console.log(msg);
    return msg;
  };

  /**
   * is string
   * @param {*} o - object to check
   * @returns {boolean} - result
   */
  const isString = o => typeof o === "string" || o instanceof String;

  /**
   * stringify positive integer
   * @param {number} i - integer
   * @param {boolean} zero - treat 0 as a positive integer
   * @returns {?string} - stringified integer
   */
  const stringifyPositiveInt = (i, zero = false) =>
    Number.isSafeInteger(i) && (zero && i >= 0 || i > 0) && `${i}` || null;

  /* windows */
  /**
   * check if any of windows is incognito
   * @returns {boolean} - result
   */
  const checkWindowIncognito = async () => {
    const windowIds = await windows.getAll({windowTypes: ["normal"]});
    let incog;
    if (windowIds && windowIds.length) {
      for (const windowId of windowIds) {
        incog = windowId.incognito;
        if (incog) {
          break;
        }
      }
    }
    return incog || false;
  };

  /* port */
  /* native application host */
  const host = runtime.connectNative(HOST);

  /**
   * port message to host
   * @param {*} msg - message
   * @returns {void}
   */
  const portHostMsg = async msg => {
    msg && host && host.postMessage(msg);
  };

  /* storage */
  /**
   * set storage
   * @param {Object} obj - object to store
   * @returns {?AsyncFunction} - store object
   */
  const setStorage = async obj => obj && storage.set(obj) || null;

  /**
   * fetch shared data and store
   * @param {string} path - data path
   * @param {string} key - storage key
   * @returns {?AsyncFunction} - set storage
   */
  const storeFetchedData = async (path, key) => {
    const data = isString(path) && isString(key) &&
                   await fetch(path).then(res => res && res.json());
    return data && setStorage({[key]: data}) || null;
  };

  /**
   * port editor config path
   * @returns {AsyncFunction} - port host message
   */
  const portEditorConfigPath = async () => {
    const data = await storage.get(EDITOR_CONFIG);
    const filePath = data && data[EDITOR_CONFIG] &&
                     data[EDITOR_CONFIG].value || "";
    return portHostMsg({[EDITOR_CONFIG_GET]: filePath});
  };

  /* content ports collection */
  const ports = {};

  /**
   * restore ports collection
   * @param {Object} data - disconnected port data
   * @returns {?AsyncFunction} - restore ports (recursive)
   */
  const restorePorts = async (data = {}) => {
    const {tabId, windowId} = data;
    let func;
    if (windowId && tabId && ports[windowId]) {
      delete ports[windowId][tabId];
      Object.keys(ports[windowId]).length === 0 &&
        (func = restorePorts({windowId}));
    } else {
      windowId && delete ports[windowId];
    }
    return func || null;
  };

  /**
   * remove port from ports collection
   * @param {!Object} port - removed port
   * @returns {void}
   */
  const removePort = async (port = {}) => {
    const {sender} = port;
    if (sender) {
      const {tab, url} = sender;
      if (tab) {
        let {windowId, id: tabId} = tab;
        tabId = stringifyPositiveInt(tabId, true);
        windowId = stringifyPositiveInt(windowId, true);
        tabId && windowId && url && ports[windowId] && ports[windowId][tabId] &&
          delete ports[windowId][tabId][url];
      }
    }
  };

  /**
   * port message
   * @param {*} msg - message
   * @param {string} windowId - windowId
   * @param {string} tabId - tabId
   * @returns {void}
   */
  const portMsg = async (msg, windowId, tabId) => {
    if (msg) {
      if (windowId && tabId) {
        const frameUrls = ports[windowId] && ports[windowId][tabId] &&
                            Object.keys(ports[windowId][tabId]);
        if (frameUrls && frameUrls.length) {
          for (const frameUrl of frameUrls) {
            const port = ports[windowId][tabId][frameUrl];
            try {
              port && port.postMessage(msg);
            } catch (e) {
              delete ports[windowId][tabId][frameUrl];
            }
          }
        }
      } else if (windowId) {
        const tabIds = ports[windowId] && Object.keys(ports[windowId]);
        if (tabIds && tabIds.length) {
          for (tabId of tabIds) {
            portMsg(msg, windowId, tabId);
          }
        }
      } else {
        const windowIds = Object.keys(ports);
        if (windowIds.length) {
          for (windowId of windowIds) {
            portMsg(msg, windowId);
          }
        }
      }
    }
  };

  /**
   * port context menu data
   * @param {!Object} info - contextMenus.OnClickData
   * @param {!Object} tab - tabs.Tab
   * @returns {void}
   */
  const portContextMenuData = async (info, tab) => {
    const {frameUrl, pageUrl} = info;
    let {windowId, id: tabId} = tab;
    windowId = stringifyPositiveInt(windowId, true);
    tabId = stringifyPositiveInt(tabId, true);
    if (windowId && tabId) {
      const port = ports[windowId] && ports[windowId][tabId] &&
                   ports[windowId][tabId][frameUrl] ||
                   ports[windowId][tabId][pageUrl];
      port && port.postMessage({
        [CONTENT_GET]: {info, tab},
      });
    }
  };

  /**
   * port sync text
   * @param {*} msg - message
   * @returns {AsyncFunction} - port message
   */
  const portSyncText = async msg => {
    let func;
    if (msg) {
      const {data} = msg;
      if (data) {
        const {tabId, windowId} = data;
        func = portMsg({[TEXT_SYNC]: msg}, windowId, tabId);
      }
    }
    return func || null;
  };

  /* icon */
  /**
   * replace icon
   * @param {string} path - icon path
   * @returns {AsyncFunction} - set icon
   */
  const replaceIcon = async (path = varsL[ICON_PATH]) =>
    browserAction.setIcon({path});

  /**
   * toggle badge
   * @param {boolean} executable - executable
   * @returns {Promise.<Array>} - results of each handler
   */
  const toggleBadge = async (executable = varsL[IS_EXECUTABLE]) => {
    const color = !executable && WARN_COLOR || [0, 0, 0, 0];
    const text = !executable && WARN_TEXT || "";
    return Promise.all([
      browserAction.setBadgeBackgroundColor({color}),
      browserAction.setBadgeText({text}),
    ]);
  };

  /* context menu */
  /* context menu items collection */
  const menus = {
    [MODE_SOURCE]: null,
    [MODE_SELECTION]: null,
    [MODE_EDIT]: null,
  };

  // TODO: implement accesskey, Issue #18
  /**
   * create context menu item
   * @param {string} id - menu item ID
   * @param {Array} contexts - contexts
   * @returns {void}
   */
  const createMenuItem = async (id, contexts) => {
    const label = varsL[EDITOR_LABEL] || LABEL;
    const accKey = !vars[IS_WEBEXT] && vars[KEY_ACCESS] &&
                   `(&${vars[KEY_ACCESS].toUpperCase()})` || "";
    isString(id) && menus.hasOwnProperty(id) && Array.isArray(contexts) && (
      menus[id] = contextMenus.create({
        id, contexts,
        title: i18n.getMessage(id, [label, accKey]),
        enabled: !!varsL[MENU_ENABLED],
      })
    );
  };

  /**
   * create context menu items
   * @returns {Promise.<Array>} - results of each handler
   */
  const createMenuItems = async () => {
    const win = await windows.getCurrent({windowTypes: ["normal"]});
    const enabled = win && (
      !win.incognito || varsL[ENABLE_PB]
    ) || false;
    const bool = enabled && !vars[ONLY_EDITABLE];
    const items = Object.keys(menus);
    const func = [];
    for (const item of items) {
      menus[item] = null;
      switch (item) {
        case MODE_EDIT:
          enabled && func.push(createMenuItem(item, ["editable"]));
          break;
        case MODE_SELECTION:
          bool && func.push(createMenuItem(item, ["selection"]));
          break;
        case MODE_SOURCE:
          bool && func.push(createMenuItem(item, ["frame", "page"]));
          break;
        default:
      }
    }
    return Promise.all(func);
  };

  /**
   * restore context menu
   * @returns {AsyncFunction} - create menu items
   */
  const restoreContextMenu = async () =>
    contextMenus.removeAll().then(createMenuItems);

  /**
   * update context menu
   * @param {Object} type - context type data
   * @returns {void}
   */
  const updateContextMenu = async type => {
    if (type) {
      const items = Object.keys(type);
      if (items.length) {
        for (const item of items) {
          const obj = type[item];
          const {menuItemId} = obj;
          if (menus[menuItemId]) {
            if (item === MODE_SOURCE) {
{'fraction_non_alphanumeric': 0.14477349676423948, 'fraction_numerical': 0.0019714567350962156, 'mean_word_length': 2.583230958230958, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '879042', 'n_tokens_mistral': 7665, 'n_tokens_neox': 7562, 'n_words': 1850}